Compare commits

..

16 Commits

Author SHA1 Message Date
lirazyehezkel
9ada330fcf Mizu enterprise preparing (#567) 2021-12-29 11:49:44 +02:00
Igor Gov
84f7107a33 API server provider readiness check with echo (#569) 2021-12-29 10:50:41 +02:00
RoyUP9
de046c65b0 Mizu install mode (#566) 2021-12-28 15:44:37 +02:00
Igor Gov
b039c2abad Fix: [EKS] server could not find the requested resourceevents.events.k8s.io (#565) 2021-12-28 13:24:51 +02:00
RoyUP9
1e1b5f0c0f move mizu agent config from config (#563) 2021-12-28 09:59:21 +02:00
RoyUP9
15895d5947 Changed sync tappers to start only when sync tappers config is true (#562) 2021-12-27 17:19:00 +02:00
RamiBerm
2da7c0f0ed TRA-4065 fix mizu tapping (#561)
* Update main.go and passive_tapper.go

* Update main.go
2021-12-27 15:11:36 +02:00
RoyUP9
06785ec877 fixed acceptance test go.sum (#560) 2021-12-27 14:45:42 +02:00
RamiBerm
cfe9e863b7 TRA-4065 support inflight tap target update (#556)
* WIP

* WIP

* Update main.go

* Update main.go and passive_tapper.go

* Update passive_tapper.go

* Update passive_tapper.go

* Update passive_tapper.go

* Update passive_tapper.go
2021-12-27 11:50:34 +02:00
RoyUP9
fd97a09624 extracted create and clean resources from tap runner (#557) 2021-12-27 11:32:48 +02:00
M. Mert Yıldıran
52ce6044ea Add 4px padding to the top of endpointServiceContainer (#555) 2021-12-26 16:20:06 +03:00
M. Mert Yıldıran
3a83531590 Use react-lowlight to highlight and json-beautify, xml-formatter to prettify the EntryBodySection (#554)
* Use `react-lowlight` to highlight and `json-beautify` to prettify the `EntryBodySection`

* Bring back the line numbers

* Make the Base64 decoding optional but make it `true` by default

* Align line numbers to right and don't have a dot character

* Make line numbers semi transparent

* Make `markers` code more elegant

* Prettify XML as well
2021-12-26 16:12:17 +03:00
M. Mert Yıldıran
e358aa4c8f Remove TCP related logging to reduce the amount of logs on DEBUG level (#553) 2021-12-26 15:10:02 +03:00
M. Mert Yıldıran
03b1313a9f Don't use Queryable for the Mime type and Encoding fields but use it directly in CollapsibleTitle suffixed components and only enable it for EntryBodySection (#550)
Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
2021-12-23 17:45:51 +03:00
M. Mert Yıldıran
32dfe40e18 Make EntryItem more responsive (#552) 2021-12-23 10:25:39 +03:00
M. Mert Yıldıran
12aaa762f6 Fix React Hook useEffect has a missing dependency: 'handleQueryChange' warning (#551) 2021-12-22 20:23:21 +03:00
50 changed files with 1144 additions and 1197 deletions

View File

@@ -179,9 +179,9 @@ tap
proxy-host: 0.0.0.0
and when changed it will support accessing by IP
### Run in daemon mode
### Install Mizu standalone
Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run
Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run
indefinitely in the cluster.
For more information please refer to [DAEMON MODE](docs/DAEMON_MODE.md)
For more information please refer to [INSTALL STANDALONE](docs/INSTALL_STANDALONE.md)

View File

@@ -110,6 +110,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@@ -156,6 +157,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@@ -191,8 +193,10 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -243,6 +247,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -288,8 +293,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
@@ -462,6 +469,7 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
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-20201110031124-69a78807bb2b/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/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=
@@ -521,6 +529,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -630,6 +639,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -641,6 +651,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
@@ -653,7 +664,9 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
k8s.io/cli-runtime v0.21.2/go.mod h1:8u/jFcM0QpoI28f6sfrAAIslLCXUYKD5SsPPMWiHYrI=
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
@@ -664,6 +677,7 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kubectl v0.21.2/go.mod h1:PgeUclpG8VVmmQIl8zpLar3IQEpFc9mrmvlwY3CK1xo=
@@ -677,5 +691,6 @@ sigs.k8s.io/kustomize/cmd/config v0.9.10/go.mod h1:Mrby0WnRH7hA6OwOYnYpfpiY0WJIM
sigs.k8s.io/kustomize/kustomize/v4 v4.1.2/go.mod h1:PxBvo4WGYlCLeRPL+ziT64wBXqbgfcalOS/SXa/tcyo=
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -880,251 +880,3 @@ func TestTapDumpLogs(t *testing.T) {
return
}
}
func TestDaemonSeeTraffic(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
}
tapDaemonCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
tapNamespace := getDefaultTapNamespace()
tapDaemonCmdArgs = append(tapDaemonCmdArgs, tapNamespace...)
tapCmd := exec.Command(cliPath, tapDaemonCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("error occured while running the tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view 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)
entries, err := getDBEntries(timestamp, entriesCount, 1*time.Second)
if err != nil {
return err
}
err = checkEntriesAtLeast(entries, 1)
if err != nil {
return err
}
entry := entries[0]
entryUrl := fmt.Sprintf("%v/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 TestDaemonMultipleNamespacesSeePods(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
expectedPods := []PodDescriptor{
{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 := getDefaultTapCommandArgsWithDaemonMode()
var namespacesCmd []string
for _, expectedPod := range expectedPods {
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
}
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
tapCmd := exec.Command(cliPath, tapCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view 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/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
}
func TestDaemonSingleNamespaceSeePods(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
expectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests"},
{Name: "httpbin2", Namespace: "mizu-tests"},
}
unexpectedPods := []PodDescriptor{
{Name: "httpbin", Namespace: "mizu-tests2"},
}
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapCmdArgs := getDefaultTapCommandArgsWithDaemonMode()
var namespacesCmd []string
for _, expectedPod := range expectedPods {
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
}
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
tapCmd := exec.Command(cliPath, tapCmdArgs...)
viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...)
t.Cleanup(func() {
daemonCleanup(t, viewCmd)
})
t.Logf("running command: %v", tapCmd.String())
if err := tapCmd.Run(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
t.Logf("running command: %v", viewCmd.String())
if err := viewCmd.Start(); err != nil {
t.Errorf("error occured while running the view 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/status/tap", apiServerUrl)
requestResult, requestErr := executeHttpGetRequest(podsUrl)
if requestErr != nil {
t.Errorf("failed to get tap status, err: %v", requestErr)
return
}
pods, err := getPods(requestResult)
if err != nil {
t.Errorf("failed to get pods, err: %v", err)
return
}
for _, unexpectedPod := range unexpectedPods {
if isPodDescriptorInPodArray(pods, unexpectedPod) {
t.Errorf("unexpected result - unexpected pod found, pod namespace: %v, pod name: %v", unexpectedPod.Namespace, unexpectedPod.Name)
return
}
}
if len(expectedPods) != len(pods) {
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
return
}
for _, expectedPod := range expectedPods {
if !isPodDescriptorInPodArray(pods, expectedPod) {
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
return
}
}
}

View File

@@ -13,7 +13,6 @@ import (
"strings"
"sync"
"syscall"
"testing"
"time"
"github.com/gorilla/websocket"
@@ -105,10 +104,6 @@ func getDefaultTapCommandArgs() []string {
return append([]string{tapCommand}, defaultCmdArgs...)
}
func getDefaultTapCommandArgsWithDaemonMode() []string {
return append(getDefaultTapCommandArgs(), "--daemon")
}
func getDefaultTapCommandArgsWithRegex(regex string) []string {
tapCommand := "tap"
defaultCmdArgs := getDefaultCommandArgs()
@@ -324,16 +319,6 @@ func getLogsPath() (string, error) {
return logsPath, nil
}
func daemonCleanup(t *testing.T, viewCmd *exec.Cmd) {
if err := runMizuClean(); err != nil {
t.Logf("error running mizu clean: %v", err)
}
if err := cleanupCommand(viewCmd); err != nil {
t.Logf("failed to cleanup view command, err: %v", err)
}
}
// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {

View File

@@ -22,6 +22,7 @@ import (
"path"
"path/filepath"
"plugin"
"regexp"
"sort"
"syscall"
"time"
@@ -55,7 +56,7 @@ var extensionsMap map[string]*tapApi.Extension // global
var startTime int64
const (
socketConnectionRetries = 10
socketConnectionRetries = 30
socketConnectionRetryDelay = time.Second * 2
socketHandshakeTimeout = time.Second * 2
)
@@ -260,20 +261,6 @@ func hostApi(socketHarOutputChannel chan<- *tapApi.OutputChannelItem) {
routes.StatusRoutes(app)
routes.NotFoundRoute(app)
if config.Config.DaemonMode {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kubernetesProvider, err := kubernetes.NewProviderInCluster()
if err != nil {
logger.Log.Fatalf("error creating k8s provider: %+v", err)
}
if _, err := startMizuTapperSyncer(ctx, kubernetesProvider); err != nil {
logger.Log.Fatalf("error initializing tapper syncer: %+v", err)
}
}
utils.StartServer(app)
}
@@ -425,25 +412,55 @@ func dialSocketWithRetry(socketAddress string, retryAmount int, retryDelay time.
time.Sleep(retryDelay)
}
} else {
go handleIncomingMessageAsTapper(socketConnection)
return socketConnection, nil
}
}
return nil, lastErr
}
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider) (*kubernetes.MizuTapperSyncer, error) {
func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
for {
if _, message, err := socketConnection.ReadMessage(); err != nil {
logger.Log.Errorf("error reading message from socket connection, err: %s, (%v,%+v)", err, err, err)
if errors.Is(err, syscall.EPIPE) {
// socket has disconnected, we can safely stop this goroutine
return
}
} else {
var socketMessageBase shared.WebSocketMessageMetadata
if err := json.Unmarshal(message, &socketMessageBase); err != nil {
logger.Log.Errorf("Could not unmarshal websocket message %v", err)
} else {
switch socketMessageBase.MessageType {
case shared.WebSocketMessageTypeTapConfig:
var tapConfigMessage *shared.WebSocketTapConfigMessage
if err := json.Unmarshal(message, &tapConfigMessage); err != nil {
logger.Log.Errorf("received unknown message from socket connection: %s, err: %s, (%v,%+v)", string(message), err, err, err)
} else {
tap.UpdateTapTargets(tapConfigMessage.TapTargets)
}
default:
logger.Log.Warningf("Received socket message of type %s for which no handlers are defined", socketMessageBase.MessageType)
}
}
}
}
}
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, istio bool) (*kubernetes.MizuTapperSyncer, error) {
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
TargetNamespaces: config.Config.TargetNamespaces,
PodFilterRegex: config.Config.TapTargetRegex.Regexp,
TargetNamespaces: targetNamespaces,
PodFilterRegex: podFilterRegex,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
AgentImage: config.Config.AgentImage,
TapperResources: config.Config.TapperResources,
ImagePullPolicy: v1.PullPolicy(config.Config.PullPolicy),
LogLevel: config.Config.LogLevel,
IgnoredUserAgents: config.Config.IgnoredUserAgents,
MizuApiFilteringOptions: config.Config.MizuApiFilteringOptions,
MizuServiceAccountExists: true, //assume service account exists since daemon mode will not function without it anyway
Istio: config.Config.Istio,
IgnoredUserAgents: ignoredUserAgents,
MizuApiFilteringOptions: mizuApiFilteringOptions,
MizuServiceAccountExists: true, //assume service account exists since install mode will not function without it anyway
Istio: istio,
}, time.Now())
if err != nil {

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/tap/api"
"io/ioutil"
"os"
)
@@ -12,7 +11,6 @@ import (
// these values are used when the config.json file is not present
const (
defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000
defaultRegexTarget string = ".*"
DefaultDatabasePath string = "./entries"
)
@@ -48,14 +46,8 @@ func applyDefaultConfig() error {
}
func getDefaultConfig() (*shared.MizuAgentConfig, error) {
regex, err := api.CompileRegexToSerializableRegexp(defaultRegexTarget)
if err != nil {
return nil, err
}
return &shared.MizuAgentConfig{
TapTargetRegex: *regex,
MaxDBSizeBytes: defaultMaxDatabaseSizeBytes,
AgentDatabasePath: DefaultDatabasePath,
DaemonMode: false,
}, nil
}

View File

@@ -2,12 +2,10 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger"
"mizuserver/pkg/api"
"mizuserver/pkg/config"
"mizuserver/pkg/holder"
"mizuserver/pkg/providers"
"mizuserver/pkg/up9"
@@ -17,13 +15,6 @@ import (
)
func HealthCheck(c *gin.Context) {
if config.Config.DaemonMode {
if providers.ExpectedTapperAmount != providers.TappersCount {
c.JSON(http.StatusInternalServerError, fmt.Sprintf("expecting more tappers than are actually connected (%d expected, %d connected)", providers.ExpectedTapperAmount, providers.TappersCount))
return
}
}
tappers := make([]shared.TapperStatus, 0)
for _, value := range providers.TappersStatus {
tappers = append(tappers, value)

View File

@@ -20,7 +20,7 @@ var (
TappersStatus map[string]shared.TapperStatus
authStatus *models.AuthStatus
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
ExpectedTapperAmount = -1 //only relevant in daemon mode as cli manages tappers otherwise
ExpectedTapperAmount = -1 //only relevant in install mode as cli manages tappers otherwise
tappersCountLock = sync.Mutex{}
)

View File

@@ -48,7 +48,8 @@ func StartServer(app *gin.Engine) {
func GetTappedPodsStatus() []shared.TappedPodStatus {
tappedPodsStatus := make([]shared.TappedPodStatus, 0)
for _, pod := range providers.TapStatus.Pods {
isTapped := strings.ToLower(providers.TappersStatus[pod.NodeName].Status) == "started"
status := strings.ToLower(providers.TappersStatus[pod.NodeName].Status)
isTapped := status == "running"
tappedPodsStatus = append(tappedPodsStatus, shared.TappedPodStatus{Name: pod.Name, Namespace: pod.Namespace, IsTapped: isTapped})
}
return tappedPodsStatus

View File

@@ -58,7 +58,7 @@ func (provider *Provider) TestConnection() error {
}
func (provider *Provider) GetHealthStatus() (*shared.HealthResponse, error) {
healthUrl := fmt.Sprintf("%s/status/health", provider.url)
healthUrl := fmt.Sprintf("%s/echo", provider.url)
if response, err := provider.client.Get(healthUrl); err != nil {
return nil, err
} else if response.StatusCode > 299 {

View File

@@ -1,6 +1,9 @@
package cmd
import "github.com/up9inc/mizu/cli/apiserver"
import (
"github.com/up9inc/mizu/cli/apiserver"
"github.com/up9inc/mizu/cli/config"
)
func performCleanCommand() {
kubernetesProvider, err := getKubernetesProviderForCli()
@@ -8,5 +11,5 @@ func performCleanCommand() {
return
}
finishMizuExecution(kubernetesProvider, apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout))
finishMizuExecution(kubernetesProvider, apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout), config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
}

View File

@@ -2,16 +2,16 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/up9inc/mizu/cli/apiserver"
"github.com/up9inc/mizu/cli/mizu"
"github.com/up9inc/mizu/cli/mizu/fsUtils"
"github.com/up9inc/mizu/cli/resources"
"github.com/up9inc/mizu/cli/telemetry"
"os"
"os/signal"
"github.com/up9inc/mizu/shared"
"path"
"syscall"
"time"
"github.com/up9inc/mizu/cli/config"
@@ -37,22 +37,6 @@ func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, 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 getKubernetesProviderForCli() (*kubernetes.Provider, error) {
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
if err != nil {
@@ -71,12 +55,12 @@ func handleKubernetesProviderError(err error) {
}
}
func finishMizuExecution(kubernetesProvider *kubernetes.Provider, apiProvider *apiserver.Provider) {
func finishMizuExecution(kubernetesProvider *kubernetes.Provider, apiProvider *apiserver.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string) {
telemetry.ReportAPICalls(apiProvider)
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
defer cancel()
dumpLogsIfNeeded(removalCtx, kubernetesProvider)
cleanUpMizuResources(removalCtx, cancel, kubernetesProvider)
resources.CleanUpMizuResources(removalCtx, cancel, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace)
}
func dumpLogsIfNeeded(ctx context.Context, kubernetesProvider *kubernetes.Provider) {
@@ -90,22 +74,11 @@ func dumpLogsIfNeeded(ctx context.Context, kubernetesProvider *kubernetes.Provid
}
}
func cleanUpMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
logger.Log.Infof("\nRemoving mizu resources")
var leftoverResources []string
if config.Config.IsNsRestrictedMode() {
leftoverResources = cleanUpRestrictedMode(ctx, kubernetesProvider)
} else {
leftoverResources = cleanUpNonRestrictedMode(ctx, cancel, kubernetesProvider)
func getSerializedMizuAgentConfig(mizuAgentConfig *shared.MizuAgentConfig) (string, error) {
serializedConfig, err := json.Marshal(mizuAgentConfig)
if err != nil {
return "", err
}
if len(leftoverResources) > 0 {
errMsg := fmt.Sprintf("Failed to remove the following resources, for more info check logs at %s:", fsUtils.GetLogFilePath())
for _, resource := range leftoverResources {
errMsg += "\n- " + resource
}
logger.Log.Errorf(uiUtils.Error, errMsg)
}
return string(serializedConfig), nil
}

21
cli/cmd/install.go Normal file
View File

@@ -0,0 +1,21 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/telemetry"
)
var installCmd = &cobra.Command{
Use: "install",
Short: "Installs mizu components",
RunE: func(cmd *cobra.Command, args []string) error {
go telemetry.ReportRun("install", nil)
runMizuInstall()
return nil
},
}
func init() {
rootCmd.AddCommand(installCmd)
}

71
cli/cmd/installRunner.go Normal file
View File

@@ -0,0 +1,71 @@
package cmd
import (
"context"
"errors"
"fmt"
"github.com/creasty/defaults"
"github.com/up9inc/mizu/cli/config"
"github.com/up9inc/mizu/cli/errormessage"
"github.com/up9inc/mizu/cli/resources"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func runMizuInstall() {
kubernetesProvider, err := getKubernetesProviderForCli()
if err != nil {
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel will be called when this function exits
var serializedValidationRules string
var serializedContract string
var defaultMaxEntriesDBSizeBytes int64 = 200 * 1000 * 1000
defaultResources := shared.Resources{}
defaults.Set(&defaultResources)
mizuAgentConfig := getInstallMizuAgentConfig(defaultMaxEntriesDBSizeBytes, defaultResources)
serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig)
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err)))
return
}
if err = resources.CreateInstallMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, nil, defaultMaxEntriesDBSizeBytes, defaultResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), false); err != nil {
var statusError *k8serrors.StatusError
if errors.As(err, &statusError) {
if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists {
logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance")
}
} else {
defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
}
return
}
logger.Log.Infof(uiUtils.Magenta, "Created Mizu Agent components, run `mizu view` to connect to the mizu daemon instance")
}
func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Resources) *shared.MizuAgentConfig {
mizuAgentConfig := shared.MizuAgentConfig{
MaxDBSizeBytes: maxDBSizeBytes,
AgentImage: config.Config.AgentImage,
PullPolicy: config.Config.ImagePullPolicyStr,
LogLevel: config.Config.LogLevel(),
TapperResources: tapperResources,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
AgentDatabasePath: shared.DataDirPath,
}
return &mizuAgentConfig
}

View File

@@ -119,6 +119,5 @@ func init() {
tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
tapCmd.Flags().String(configStructs.ContractFile, defaultTapConfig.ContractFile, "OAS/Swagger file to validate to monitor the contracts")
tapCmd.Flags().Bool(configStructs.DaemonModeTapName, defaultTapConfig.DaemonMode, "Run mizu in daemon mode, detached from the cli")
tapCmd.Flags().Bool(configStructs.IstioName, defaultTapConfig.Istio, "Record decrypted traffic if the cluster configured with istio and mtls")
}

View File

@@ -4,26 +4,24 @@ import (
"context"
"errors"
"fmt"
"github.com/up9inc/mizu/cli/resources"
"github.com/up9inc/mizu/cli/utils"
"io/ioutil"
"regexp"
"strings"
"time"
"k8s.io/apimachinery/pkg/util/intstr"
"github.com/getkin/kin-openapi/openapi3"
"gopkg.in/yaml.v3"
core "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/up9inc/mizu/cli/apiserver"
"github.com/up9inc/mizu/cli/cmd/goUtils"
"github.com/up9inc/mizu/cli/config"
"github.com/up9inc/mizu/cli/config/configStructs"
"github.com/up9inc/mizu/cli/errormessage"
"github.com/up9inc/mizu/cli/mizu"
"github.com/up9inc/mizu/cli/mizu/fsUtils"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared"
@@ -35,11 +33,8 @@ import (
const cleanupTimeout = time.Minute
type tapState struct {
startTime time.Time
targetNamespaces []string
apiServerService *core.Service
tapperSyncer *kubernetes.MizuTapperSyncer
startTime time.Time
targetNamespaces []string
mizuServiceAccountExists bool
}
@@ -49,13 +44,9 @@ var apiProvider *apiserver.Provider
func RunMizuTap() {
state.startTime = time.Now()
mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
apiProvider = apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout)
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err)))
return
}
var err error
var serializedValidationRules string
if config.Config.Tap.EnforcePolicyFile != "" {
serializedValidationRules, err = readValidationRules(config.Config.Tap.EnforcePolicyFile)
@@ -99,9 +90,10 @@ func RunMizuTap() {
state.targetNamespaces = getNamespaces(kubernetesProvider)
serializedMizuConfig, err := config.GetSerializedMizuAgentConfig(state.targetNamespaces, mizuApiFilteringOptions)
mizuAgentConfig := getTapMizuAgentConfig()
serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig)
if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error composing mizu config: %v", errormessage.FormatError(err)))
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err)))
return
}
@@ -131,43 +123,41 @@ func RunMizuTap() {
}
logger.Log.Infof("Waiting for Mizu Agent to start...")
if err := createMizuResources(ctx, cancel, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
if state.mizuServiceAccountExists, err = resources.CreateTapMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel()); err != nil {
var statusError *k8serrors.StatusError
if errors.As(err, &statusError) {
if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists {
logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance")
}
} else {
defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
}
return
}
if config.Config.Tap.DaemonMode {
if err := handleDaemonModePostCreation(ctx, cancel, kubernetesProvider, state.targetNamespaces); err != nil {
defer finishMizuExecution(kubernetesProvider, apiProvider)
cancel()
} else {
logger.Log.Infof(uiUtils.Magenta, "Mizu is now running in daemon mode, run `mizu view` to connect to the mizu daemon instance")
}
} else {
defer finishMizuExecution(kubernetesProvider, apiProvider)
go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel)
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
// block until exit signal or error
waitForFinish(ctx, cancel)
}
go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel)
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
// block until exit signal or error
utils.WaitForFinish(ctx, cancel)
}
func handleDaemonModePostCreation(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, namespaces []string) error {
apiProvider := apiserver.NewProvider(GetApiServerUrl(), 90, 1*time.Second)
if err := waitForDaemonModeToBeReady(cancel, kubernetesProvider, apiProvider); err != nil {
return err
func getTapMizuAgentConfig() *shared.MizuAgentConfig {
mizuAgentConfig := shared.MizuAgentConfig{
MaxDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(),
AgentImage: config.Config.AgentImage,
PullPolicy: config.Config.ImagePullPolicyStr,
LogLevel: config.Config.LogLevel(),
TapperResources: config.Config.Tap.TapperResources,
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
AgentDatabasePath: shared.DataDirPath,
}
return nil
return &mizuAgentConfig
}
/*
@@ -189,17 +179,6 @@ func printTappedPodsPreview(ctx context.Context, kubernetesProvider *kubernetes.
}
}
func waitForDaemonModeToBeReady(cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, apiProvider *apiserver.Provider) error {
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
// TODO: TRA-3903 add a smarter test to see that tapping/pod watching is functioning properly
if err := apiProvider.TestConnection(); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Mizu was not ready in time, for more info check logs at %s", fsUtils.GetLogFilePath()))
return err
}
return nil
}
func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, mizuApiFilteringOptions api.TrafficFilteringOptions, startTime time.Time) error {
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
TargetNamespaces: targetNamespaces,
@@ -252,8 +231,6 @@ func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider
}
}()
state.tapperSyncer = tapperSyncer
return nil
}
@@ -287,137 +264,6 @@ func readValidationRules(file string) (string, error) {
return string(newContent), nil
}
func createMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string) error {
if !config.Config.IsNsRestrictedMode() {
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
return err
}
}
if err := createMizuConfigmap(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig); err != nil {
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v", errormessage.FormatError(err)))
}
var err error
state.mizuServiceAccountExists, err = createRBACIfNecessary(ctx, kubernetesProvider)
if err != nil {
if !config.Config.Tap.DaemonMode {
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
if state.mizuServiceAccountExists {
serviceAccountName = kubernetes.ServiceAccountName
} else {
serviceAccountName = ""
}
opts := &kubernetes.ApiServerOptions{
Namespace: config.Config.MizuResourcesNamespace,
PodName: kubernetes.ApiServerPodName,
PodImage: config.Config.AgentImage,
ServiceAccountName: serviceAccountName,
IsNamespaceRestricted: config.Config.IsNsRestrictedMode(),
SyncEntriesConfig: getSyncEntriesConfig(),
MaxEntriesDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(),
Resources: config.Config.Tap.ApiServerResources,
ImagePullPolicy: config.Config.ImagePullPolicy(),
LogLevel: config.Config.LogLevel(),
}
if config.Config.Tap.DaemonMode {
if !state.mizuServiceAccountExists {
defer cleanUpMizuResources(ctx, cancel, kubernetesProvider)
logger.Log.Fatalf(uiUtils.Red, fmt.Sprintf("Failed to ensure the resources required for mizu to run in daemon mode. cannot proceed. error: %v", errormessage.FormatError(err)))
}
if err := createMizuApiServerDeployment(ctx, kubernetesProvider, opts); err != nil {
return err
}
} else {
if err := createMizuApiServerPod(ctx, kubernetesProvider, opts); err != nil {
return err
}
}
state.apiServerService, err = kubernetesProvider.CreateService(ctx, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName)
if err != nil {
return err
}
logger.Log.Debugf("Successfully created service: %s", kubernetes.ApiServerPodName)
return nil
}
func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string) error {
err := kubernetesProvider.CreateConfigMap(ctx, config.Config.MizuResourcesNamespace, kubernetes.ConfigMapName, serializedValidationRules, serializedContract, serializedMizuConfig)
return err
}
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider) error {
_, err := kubernetesProvider.CreateNamespace(ctx, config.Config.MizuResourcesNamespace)
return err
}
func createMizuApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions) error {
pod, err := kubernetesProvider.GetMizuApiServerPodObject(opts, false, "")
if err != nil {
return err
}
if _, err = kubernetesProvider.CreatePod(ctx, config.Config.MizuResourcesNamespace, pod); err != nil {
return err
}
logger.Log.Debugf("Successfully created API server pod: %s", kubernetes.ApiServerPodName)
return nil
}
func createMizuApiServerDeployment(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions) error {
volumeClaimCreated := false
if !config.Config.Tap.NoPersistentVolumeClaim {
volumeClaimCreated = TryToCreatePersistentVolumeClaim(ctx, kubernetesProvider)
}
pod, err := kubernetesProvider.GetMizuApiServerPodObject(opts, volumeClaimCreated, kubernetes.PersistentVolumeClaimName)
if err != nil {
return err
}
pod.Spec.Containers[0].LivenessProbe = &core.Probe{
Handler: core.Handler{
HTTPGet: &core.HTTPGetAction{
Path: "/echo",
Port: intstr.FromInt(shared.DefaultApiServerPort),
},
},
InitialDelaySeconds: 1,
PeriodSeconds: 10,
}
if _, err = kubernetesProvider.CreateDeployment(ctx, config.Config.MizuResourcesNamespace, opts.PodName, pod); err != nil {
return err
}
logger.Log.Debugf("Successfully created API server deployment: %s", kubernetes.ApiServerPodName)
return nil
}
func TryToCreatePersistentVolumeClaim(ctx context.Context, kubernetesProvider *kubernetes.Provider) bool {
isDefaultStorageClassAvailable, err := kubernetesProvider.IsDefaultStorageProviderAvailable(ctx)
if err != nil {
logger.Log.Warningf(uiUtils.Yellow, "An error occured when checking if a default storage provider exists in this cluster, this means mizu data will be lost on mizu-api-server pod restart")
logger.Log.Debugf("error checking if default storage class exists: %v", err)
return false
} else if !isDefaultStorageClassAvailable {
logger.Log.Warningf(uiUtils.Yellow, "Could not find default storage provider in this cluster, this means mizu data will be lost on mizu-api-server pod restart")
return false
}
if _, err = kubernetesProvider.CreatePersistentVolumeClaim(ctx, config.Config.MizuResourcesNamespace, kubernetes.PersistentVolumeClaimName, config.Config.Tap.MaxEntriesDBSizeBytes()+mizu.DaemonModePersistentVolumeSizeBufferBytes); err != nil {
logger.Log.Warningf(uiUtils.Yellow, "An error has occured while creating a persistent volume claim for mizu, this means mizu data will be lost on mizu-api-server pod restart")
logger.Log.Debugf("error creating persistent volume claim: %v", err)
return false
}
return true
}
func getMizuApiFilteringOptions() (*api.TrafficFilteringOptions, error) {
var compiledRegexSlice []*api.SerializableRegexp
@@ -452,114 +298,6 @@ func getSyncEntriesConfig() *shared.SyncEntriesConfig {
}
}
func cleanUpRestrictedMode(ctx context.Context, kubernetesProvider *kubernetes.Provider) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemoveService(ctx, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Service %s in namespace %s", kubernetes.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveDaemonSet(ctx, config.Config.MizuResourcesNamespace, kubernetes.TapperDaemonSetName); err != nil {
resourceDesc := fmt.Sprintf("DaemonSet %s in namespace %s", kubernetes.TapperDaemonSetName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveConfigMap(ctx, config.Config.MizuResourcesNamespace, kubernetes.ConfigMapName); err != nil {
resourceDesc := fmt.Sprintf("ConfigMap %s in namespace %s", kubernetes.ConfigMapName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveServicAccount(ctx, config.Config.MizuResourcesNamespace, kubernetes.ServiceAccountName); err != nil {
resourceDesc := fmt.Sprintf("Service Account %s in namespace %s", kubernetes.ServiceAccountName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRole(ctx, config.Config.MizuResourcesNamespace, kubernetes.RoleName); err != nil {
resourceDesc := fmt.Sprintf("Role %s in namespace %s", kubernetes.RoleName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemovePod(ctx, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Pod %s in namespace %s", kubernetes.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
//daemon mode resources
if err := kubernetesProvider.RemoveRoleBinding(ctx, config.Config.MizuResourcesNamespace, kubernetes.RoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", kubernetes.RoleBindingName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveDeployment(ctx, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Deployment %s in namespace %s", kubernetes.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemovePersistentVolumeClaim(ctx, config.Config.MizuResourcesNamespace, kubernetes.PersistentVolumeClaimName); err != nil {
resourceDesc := fmt.Sprintf("PersistentVolumeClaim %s in namespace %s", kubernetes.PersistentVolumeClaimName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRole(ctx, config.Config.MizuResourcesNamespace, kubernetes.DaemonRoleName); err != nil {
resourceDesc := fmt.Sprintf("Role %s in namespace %s", kubernetes.DaemonRoleName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRoleBinding(ctx, config.Config.MizuResourcesNamespace, kubernetes.DaemonRoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", kubernetes.DaemonRoleBindingName, 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, kubernetes.ClusterRoleName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRole %s", kubernetes.ClusterRoleName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveClusterRoleBinding(ctx, kubernetes.ClusterRoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRoleBinding %s", kubernetes.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.
go func() {
waitForFinish(ctx, cancel)
}()
if err := kubernetesProvider.WaitUtilNamespaceDeleted(ctx, config.Config.MizuResourcesNamespace); err != nil {
switch {
case ctx.Err() == context.Canceled:
logger.Log.Debugf("Do nothing. User interrupted the wait")
case err == wait.ErrWaitTimeout:
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Timeout while removing Namespace %s", config.Config.MizuResourcesNamespace))
default:
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error while waiting for Namespace %s to be deleted: %v", config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
}
}
func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", kubernetes.ApiServerPodName))
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
@@ -635,7 +373,8 @@ func watchApiServerEvents(ctx context.Context, kubernetesProvider *kubernetes.Pr
event, err := wEvent.ToEvent()
if err != nil {
logger.Log.Errorf(fmt.Sprintf("Error parsing Mizu resource event: %+v", err))
logger.Log.Debugf("[ERROR] parsing Mizu resource event: %+v", err)
continue
}
if state.startTime.After(event.CreationTimestamp.Time) {
@@ -663,7 +402,7 @@ func watchApiServerEvents(ctx context.Context, kubernetesProvider *kubernetes.Pr
continue
}
logger.Log.Errorf("Watching API server events loop, error: %+v", err)
logger.Log.Debugf("[Error] Watching API server events loop, error: %+v", err)
case <-ctx.Done():
logger.Log.Debugf("Watching API server events loop, ctx done")
return
@@ -705,21 +444,3 @@ func getNamespaces(kubernetesProvider *kubernetes.Provider) []string {
return []string{currentNamespace}
}
}
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) (bool, error) {
if !config.Config.IsNsRestrictedMode() {
if err := kubernetesProvider.CreateMizuRBAC(ctx, config.Config.MizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.ClusterRoleName, kubernetes.ClusterRoleBindingName, mizu.RBACVersion); err != nil {
return false, err
}
} else {
if err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, config.Config.MizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.RoleName, kubernetes.RoleBindingName, mizu.RBACVersion); err != nil {
return false, err
}
}
if config.Config.Tap.DaemonMode {
if err := kubernetesProvider.CreateDaemonsetRBAC(ctx, config.Config.MizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.DaemonRoleName, kubernetes.DaemonRoleBindingName, mizu.RBACVersion); err != nil {
return false, err
}
}
return true, nil
}

View File

@@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"github.com/up9inc/mizu/cli/utils"
"net/http"
"github.com/up9inc/mizu/cli/apiserver"
@@ -71,5 +72,5 @@ func runMizuView() {
return
}
waitForFinish(ctx, cancel)
utils.WaitForFinish(ctx, cancel)
}

View File

@@ -9,9 +9,6 @@ import (
"strconv"
"strings"
"github.com/up9inc/mizu/tap/api"
"k8s.io/apimachinery/pkg/util/json"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/logger"
@@ -371,38 +368,3 @@ func setZeroForReadonlyFields(currentElem reflect.Value) {
}
}
}
func GetSerializedMizuAgentConfig(targetNamespaces []string, mizuApiFilteringOptions *api.TrafficFilteringOptions) (string, error) {
mizuConfig, err := getMizuAgentConfig(targetNamespaces, mizuApiFilteringOptions)
if err != nil {
return "", err
}
serializedConfig, err := json.Marshal(mizuConfig)
if err != nil {
return "", err
}
return string(serializedConfig), nil
}
func getMizuAgentConfig(targetNamespaces []string, mizuApiFilteringOptions *api.TrafficFilteringOptions) (*shared.MizuAgentConfig, error) {
serializableRegex, err := api.CompileRegexToSerializableRegexp(Config.Tap.PodRegexStr)
if err != nil {
return nil, err
}
config := shared.MizuAgentConfig{
TapTargetRegex: *serializableRegex,
MaxDBSizeBytes: Config.Tap.MaxEntriesDBSizeBytes(),
DaemonMode: Config.Tap.DaemonMode,
TargetNamespaces: targetNamespaces,
AgentImage: Config.AgentImage,
PullPolicy: Config.ImagePullPolicyStr,
LogLevel: Config.LogLevel(),
IgnoredUserAgents: Config.Tap.IgnoredUserAgents,
TapperResources: Config.Tap.TapperResources,
MizuResourcesNamespace: Config.MizuResourcesNamespace,
MizuApiFilteringOptions: *mizuApiFilteringOptions,
AgentDatabasePath: shared.DataDirPath,
Istio: Config.Tap.Istio,
}
return &config, nil
}

View File

@@ -3,8 +3,6 @@ package configStructs
import (
"errors"
"fmt"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared/logger"
"regexp"
"github.com/up9inc/mizu/shared"
@@ -24,7 +22,6 @@ const (
WorkspaceTapName = "workspace"
EnforcePolicyFile = "traffic-validation-file"
ContractFile = "contract"
DaemonModeTapName = "daemon"
IstioName = "istio"
)
@@ -47,8 +44,6 @@ type TapConfig struct {
AskUploadConfirmation bool `yaml:"ask-upload-confirmation" default:"true"`
ApiServerResources shared.Resources `yaml:"api-server-resources"`
TapperResources shared.Resources `yaml:"tapper-resources"`
DaemonMode bool `yaml:"daemon" default:"false"`
NoPersistentVolumeClaim bool `yaml:"no-persistent-volume-claim" default:"false"`
Istio bool `yaml:"istio" default:"false"`
}
@@ -84,9 +79,5 @@ func (config *TapConfig) Validate() error {
return errors.New(fmt.Sprintf("Can't run with both --%s and --%s flags", AnalysisTapName, WorkspaceTapName))
}
if config.NoPersistentVolumeClaim && !config.DaemonMode {
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("the --set tap.no-persistent-volume-claim=true flag has no effect without the --%s flag, the claim will not be created anyway.", DaemonModeTapName))
}
return nil
}

View File

@@ -6,13 +6,13 @@ import (
)
var (
SemVer = "0.0.1"
Branch = "develop"
GitCommitHash = "" // this var is overridden using ldflags in makefile when building
BuildTimestamp = "" // this var is overridden using ldflags in makefile when building
RBACVersion = "v1"
Platform = ""
DaemonModePersistentVolumeSizeBufferBytes = int64(500 * 1000 * 1000) //500mb
SemVer = "0.0.1"
Branch = "develop"
GitCommitHash = "" // this var is overridden using ldflags in makefile when building
BuildTimestamp = "" // this var is overridden using ldflags in makefile when building
RBACVersion = "v1"
Platform = ""
InstallModePersistentVolumeSizeBufferBytes = int64(500 * 1000 * 1000) //500mb
)
const DEVENVVAR = "MIZU_DISABLE_TELEMTRY"

View File

@@ -0,0 +1,141 @@
package resources
import (
"context"
"fmt"
"github.com/up9inc/mizu/cli/errormessage"
"github.com/up9inc/mizu/cli/mizu/fsUtils"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/cli/utils"
"github.com/up9inc/mizu/shared/kubernetes"
"github.com/up9inc/mizu/shared/logger"
"k8s.io/apimachinery/pkg/util/wait"
)
func CleanUpMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string) {
logger.Log.Infof("\nRemoving mizu resources")
var leftoverResources []string
if isNsRestrictedMode {
leftoverResources = cleanUpRestrictedMode(ctx, kubernetesProvider, mizuResourcesNamespace)
} else {
leftoverResources = cleanUpNonRestrictedMode(ctx, cancel, kubernetesProvider, mizuResourcesNamespace)
}
if len(leftoverResources) > 0 {
errMsg := fmt.Sprintf("Failed to remove the following resources, for more info check logs at %s:", fsUtils.GetLogFilePath())
for _, resource := range leftoverResources {
errMsg += "\n- " + resource
}
logger.Log.Errorf(uiUtils.Error, errMsg)
}
}
func cleanUpNonRestrictedMode(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemoveNamespace(ctx, mizuResourcesNamespace); err != nil {
resourceDesc := fmt.Sprintf("Namespace %s", mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
} else {
defer waitUntilNamespaceDeleted(ctx, cancel, kubernetesProvider, mizuResourcesNamespace)
}
if err := kubernetesProvider.RemoveClusterRole(ctx, kubernetes.ClusterRoleName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRole %s", kubernetes.ClusterRoleName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveClusterRoleBinding(ctx, kubernetes.ClusterRoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRoleBinding %s", kubernetes.ClusterRoleBindingName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
return leftoverResources
}
func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) {
// Call cancel if a terminating signal was received. Allows user to skip the wait.
go func() {
utils.WaitForFinish(ctx, cancel)
}()
if err := kubernetesProvider.WaitUtilNamespaceDeleted(ctx, mizuResourcesNamespace); err != nil {
switch {
case ctx.Err() == context.Canceled:
logger.Log.Debugf("Do nothing. User interrupted the wait")
case err == wait.ErrWaitTimeout:
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Timeout while removing Namespace %s", mizuResourcesNamespace))
default:
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error while waiting for Namespace %s to be deleted: %v", mizuResourcesNamespace, errormessage.FormatError(err)))
}
}
}
func cleanUpRestrictedMode(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemoveService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Service %s in namespace %s", kubernetes.ApiServerPodName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveDaemonSet(ctx, mizuResourcesNamespace, kubernetes.TapperDaemonSetName); err != nil {
resourceDesc := fmt.Sprintf("DaemonSet %s in namespace %s", kubernetes.TapperDaemonSetName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveConfigMap(ctx, mizuResourcesNamespace, kubernetes.ConfigMapName); err != nil {
resourceDesc := fmt.Sprintf("ConfigMap %s in namespace %s", kubernetes.ConfigMapName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveServicAccount(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName); err != nil {
resourceDesc := fmt.Sprintf("Service Account %s in namespace %s", kubernetes.ServiceAccountName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRole(ctx, mizuResourcesNamespace, kubernetes.RoleName); err != nil {
resourceDesc := fmt.Sprintf("Role %s in namespace %s", kubernetes.RoleName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemovePod(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Pod %s in namespace %s", kubernetes.ApiServerPodName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
//install mode resources
if err := kubernetesProvider.RemoveRoleBinding(ctx, mizuResourcesNamespace, kubernetes.RoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", kubernetes.RoleBindingName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveDeployment(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Deployment %s in namespace %s", kubernetes.ApiServerPodName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemovePersistentVolumeClaim(ctx, mizuResourcesNamespace, kubernetes.PersistentVolumeClaimName); err != nil {
resourceDesc := fmt.Sprintf("PersistentVolumeClaim %s in namespace %s", kubernetes.PersistentVolumeClaimName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRole(ctx, mizuResourcesNamespace, kubernetes.DaemonRoleName); err != nil {
resourceDesc := fmt.Sprintf("Role %s in namespace %s", kubernetes.DaemonRoleName, mizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRoleBinding(ctx, mizuResourcesNamespace, kubernetes.DaemonRoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", kubernetes.DaemonRoleBindingName, mizuResourcesNamespace)
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)
}

View File

@@ -0,0 +1,198 @@
package resources
import (
"context"
"fmt"
"github.com/op/go-logging"
"github.com/up9inc/mizu/cli/errormessage"
"github.com/up9inc/mizu/cli/mizu"
"github.com/up9inc/mizu/cli/uiUtils"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/kubernetes"
"github.com/up9inc/mizu/shared/logger"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level) (bool, error) {
if !isNsRestrictedMode {
if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil {
return false, err
}
}
if err := createMizuConfigmap(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, mizuResourcesNamespace); err != nil {
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v", errormessage.FormatError(err)))
}
mizuServiceAccountExists, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace)
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
if mizuServiceAccountExists {
serviceAccountName = kubernetes.ServiceAccountName
} else {
serviceAccountName = ""
}
opts := &kubernetes.ApiServerOptions{
Namespace: mizuResourcesNamespace,
PodName: kubernetes.ApiServerPodName,
PodImage: agentImage,
ServiceAccountName: serviceAccountName,
IsNamespaceRestricted: isNsRestrictedMode,
SyncEntriesConfig: syncEntriesConfig,
MaxEntriesDBSizeBytes: maxEntriesDBSizeBytes,
Resources: apiServerResources,
ImagePullPolicy: imagePullPolicy,
LogLevel: logLevel,
}
if err := createMizuApiServerPod(ctx, kubernetesProvider, opts); err != nil {
return mizuServiceAccountExists, err
}
_, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName)
if err != nil {
return mizuServiceAccountExists, err
}
logger.Log.Debugf("Successfully created service: %s", kubernetes.ApiServerPodName)
return mizuServiceAccountExists, nil
}
func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) error {
if !isNsRestrictedMode {
if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil {
return err
}
logger.Log.Infof("Created mizu namespace")
}
if err := createMizuConfigmap(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, mizuResourcesNamespace); err != nil {
return err
}
logger.Log.Infof("Created config map")
_, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace)
if err != nil {
return err
}
if err := kubernetesProvider.CreateDaemonsetRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.DaemonRoleName, kubernetes.DaemonRoleBindingName, mizu.RBACVersion); err != nil {
return err
}
logger.Log.Infof("Created RBAC")
serviceAccountName := kubernetes.ServiceAccountName
opts := &kubernetes.ApiServerOptions{
Namespace: mizuResourcesNamespace,
PodName: kubernetes.ApiServerPodName,
PodImage: agentImage,
ServiceAccountName: serviceAccountName,
IsNamespaceRestricted: isNsRestrictedMode,
SyncEntriesConfig: syncEntriesConfig,
MaxEntriesDBSizeBytes: maxEntriesDBSizeBytes,
Resources: apiServerResources,
ImagePullPolicy: imagePullPolicy,
LogLevel: logLevel,
}
if err := createMizuApiServerDeployment(ctx, kubernetesProvider, opts, noPersistentVolumeClaim); err != nil {
return err
}
logger.Log.Infof("Created Api Server deployment")
_, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName)
if err != nil {
return err
}
logger.Log.Infof("Created Api Server service")
return nil
}
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) error {
_, err := kubernetesProvider.CreateNamespace(ctx, mizuResourcesNamespace)
return err
}
func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, mizuResourcesNamespace string) error {
err := kubernetesProvider.CreateConfigMap(ctx, mizuResourcesNamespace, kubernetes.ConfigMapName, serializedValidationRules, serializedContract, serializedMizuConfig)
return err
}
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string) (bool, error) {
if !isNsRestrictedMode {
if err := kubernetesProvider.CreateMizuRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.ClusterRoleName, kubernetes.ClusterRoleBindingName, mizu.RBACVersion); err != nil {
return false, err
}
} else {
if err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.RoleName, kubernetes.RoleBindingName, mizu.RBACVersion); err != nil {
return false, err
}
}
return true, nil
}
func createMizuApiServerDeployment(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions, noPersistentVolumeClaim bool) error {
volumeClaimCreated := false
if !noPersistentVolumeClaim {
volumeClaimCreated = tryToCreatePersistentVolumeClaim(ctx, kubernetesProvider, opts)
}
pod, err := kubernetesProvider.GetMizuApiServerPodObject(opts, volumeClaimCreated, kubernetes.PersistentVolumeClaimName)
if err != nil {
return err
}
pod.Spec.Containers[0].LivenessProbe = &core.Probe{
Handler: core.Handler{
HTTPGet: &core.HTTPGetAction{
Path: "/echo",
Port: intstr.FromInt(shared.DefaultApiServerPort),
},
},
InitialDelaySeconds: 1,
PeriodSeconds: 10,
}
if _, err = kubernetesProvider.CreateDeployment(ctx, opts.Namespace, opts.PodName, pod); err != nil {
return err
}
logger.Log.Debugf("Successfully created API server deployment: %s", kubernetes.ApiServerPodName)
return nil
}
func tryToCreatePersistentVolumeClaim(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions) bool {
isDefaultStorageClassAvailable, err := kubernetesProvider.IsDefaultStorageProviderAvailable(ctx)
if err != nil {
logger.Log.Warningf(uiUtils.Yellow, "An error occured when checking if a default storage provider exists in this cluster, this means mizu data will be lost on mizu-api-server pod restart")
logger.Log.Debugf("error checking if default storage class exists: %v", err)
return false
} else if !isDefaultStorageClassAvailable {
logger.Log.Warningf(uiUtils.Yellow, "Could not find default storage provider in this cluster, this means mizu data will be lost on mizu-api-server pod restart")
return false
}
if _, err = kubernetesProvider.CreatePersistentVolumeClaim(ctx, opts.Namespace, kubernetes.PersistentVolumeClaimName, opts.MaxEntriesDBSizeBytes + mizu.InstallModePersistentVolumeSizeBufferBytes); err != nil {
logger.Log.Warningf(uiUtils.Yellow, "An error has occured while creating a persistent volume claim for mizu, this means mizu data will be lost on mizu-api-server pod restart")
logger.Log.Debugf("error creating persistent volume claim: %v", err)
return false
}
return true
}
func createMizuApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, opts *kubernetes.ApiServerOptions) error {
pod, err := kubernetesProvider.GetMizuApiServerPodObject(opts, false, "")
if err != nil {
return err
}
if _, err = kubernetesProvider.CreatePod(ctx, opts.Namespace, pod); err != nil {
return err
}
logger.Log.Debugf("Successfully created API server pod: %s", kubernetes.ApiServerPodName)
return nil
}

25
cli/utils/waitUtils.go Normal file
View File

@@ -0,0 +1,25 @@
package utils
import (
"context"
"github.com/up9inc/mizu/shared/logger"
"os"
"os/signal"
"syscall"
)
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()
}
}

View File

@@ -38,8 +38,6 @@ Please make sure to use full option name (`tap.dry-run` as opposed to `dry-run`
* `all-namespaces` - special flag indicating whether Mizu should search and tap pods, matching the regex, in all namespaces. Default is `false`. Please use with caution, tapping too many pods can affect resource consumption.
* `daemon` - instructs Mizu whether to run daemon mode (where CLI command exits after launch, and tapper & api-server pods in Kubernetes continue to run without controlling CLI). Typically supplied as command-line option `--daemon`. Default valie is `false`
* `dry-run` - if true, Mizu will print list of pods matching the supplied (or default) regex and exit without actually tapping the traffic. Default value is `false`. Typically supplied as command-line option `--dry-run`
* `proxy-host` - IP address on which proxy to Mizu API service is launched; should be accessible at `proxy-host:gui-port`. Default value is `127.0.0.1`

View File

@@ -1,22 +1,16 @@
# Mizu daemon mode
# Mizu install standalone
Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run
Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run
indefinitely in the cluster.
Please note that daemon mode requires you to have RBAC creation permissions, see the [permissions](PERMISSIONS.md)
Please note that install standalone requires you to have RBAC creation permissions, see the [permissions](PERMISSIONS.md)
doc for more details.
```bash
$ mizu tap "^ca.*" --daemon
Mizu will store up to 200MB of traffic, old traffic will be cleared once the limit is reached.
Tapping pods in namespaces "sock-shop"
Waiting for mizu to be ready... (may take a few minutes)
+carts-66c77f5fbb-fq65r
+catalogue-5f4cb7cf5-7zrmn
..
$ mizu install
```
## Stop mizu daemon
## Stop mizu install
To stop the detached mizu instance and clean all cluster side resources, run `mizu clean`

View File

@@ -57,11 +57,11 @@ Mizu needs following permissions on your Kubernetes cluster to run properly
- get
```
## Permissions required running with --daemon flag or (optional) for service / pod name resolving
## Permissions required running with install command or (optional) for service / pod name resolving
Mandatory permissions for running with `--daemon` flag.
Mandatory permissions for running with install command.
Optional for service/pod name resolving in non daemon mode
Optional for service/pod name resolving in non install standalone
```yaml
- apiGroups:

View File

@@ -70,9 +70,49 @@ func CreateAndStartMizuTapperSyncer(ctx context.Context, kubernetesProvider *Pro
go syncer.watchPodsForTapping()
go syncer.watchTapperEvents()
go syncer.watchTapperPods()
return syncer, nil
}
func (tapperSyncer *MizuTapperSyncer) watchTapperPods() {
mizuResourceRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", TapperPodName))
podWatchHelper := NewPodWatchHelper(tapperSyncer.kubernetesProvider, mizuResourceRegex)
eventChan, errorChan := FilteredWatch(tapperSyncer.context, podWatchHelper, []string{tapperSyncer.config.MizuResourcesNamespace}, podWatchHelper)
for {
select {
case wEvent, ok := <-eventChan:
if !ok {
eventChan = nil
continue
}
pod, err := wEvent.ToPod()
if err != nil {
logger.Log.Debugf("[ERROR] parsing Mizu resource pod: %+v", err)
continue
}
logger.Log.Debugf("Watching tapper pods loop, tapper: %v, node: %v, status: %v", pod.Name, pod.Spec.NodeName, pod.Status.Phase)
if pod.Spec.NodeName != "" {
tapperStatus := shared.TapperStatus{TapperName: pod.Name, NodeName: pod.Spec.NodeName, Status: string(pod.Status.Phase)}
tapperSyncer.TapperStatusChangedOut <- tapperStatus
}
case err, ok := <-errorChan:
if !ok {
errorChan = nil
continue
}
logger.Log.Debugf("[ERROR] Watching tapper pods loop, error: %+v", err)
case <-tapperSyncer.context.Done():
logger.Log.Debugf("Watching tapper pods loop, ctx done")
return
}
}
}
func (tapperSyncer *MizuTapperSyncer) watchTapperEvents() {
mizuResourceRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", TapperPodName))
eventWatchHelper := NewEventWatchHelper(tapperSyncer.kubernetesProvider, mizuResourceRegex, "pod")
@@ -88,7 +128,8 @@ func (tapperSyncer *MizuTapperSyncer) watchTapperEvents() {
event, err := wEvent.ToEvent()
if err != nil {
logger.Log.Errorf(fmt.Sprintf("Error parsing Mizu resource event: %+v", err))
logger.Log.Debugf("[ERROR] parsing Mizu resource event: %+v", err)
continue
}
if tapperSyncer.startTime.After(event.CreationTimestamp.Time) {
@@ -117,8 +158,8 @@ func (tapperSyncer *MizuTapperSyncer) watchTapperEvents() {
nodeName = pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields[0].Values[0]
}
taperStatus := shared.TapperStatus{TapperName: pod.Name, NodeName: nodeName, Status: event.Reason}
tapperSyncer.TapperStatusChangedOut <- taperStatus
tapperStatus := shared.TapperStatus{TapperName: pod.Name, NodeName: nodeName, Status: string(pod.Status.Phase)}
tapperSyncer.TapperStatusChangedOut <- tapperStatus
case err, ok := <-errorChan:
if !ok {
@@ -126,7 +167,7 @@ func (tapperSyncer *MizuTapperSyncer) watchTapperEvents() {
continue
}
logger.Log.Errorf("Watching tapper events loop, error: %+v", err)
logger.Log.Debugf("[ERROR] Watching tapper events loop, error: %+v", err)
case <-tapperSyncer.context.Done():
logger.Log.Debugf("Watching tapper events loop, ctx done")

View File

@@ -1,12 +1,13 @@
package shared
import (
"github.com/op/go-logging"
"github.com/up9inc/mizu/shared/logger"
"github.com/up9inc/mizu/tap/api"
"io/ioutil"
"strings"
"github.com/op/go-logging"
"github.com/up9inc/mizu/shared/logger"
v1 "k8s.io/api/core/v1"
"gopkg.in/yaml.v3"
)
@@ -21,6 +22,7 @@ const (
WebSocketMessageTypeToast WebSocketMessageType = "toast"
WebSocketMessageTypeQueryMetadata WebSocketMessageType = "queryMetadata"
WebSocketMessageTypeStartTime WebSocketMessageType = "startTime"
WebSocketMessageTypeTapConfig WebSocketMessageType = "tapConfig"
)
type Resources struct {
@@ -31,19 +33,13 @@ type Resources struct {
}
type MizuAgentConfig struct {
TapTargetRegex api.SerializableRegexp `json:"tapTargetRegex"`
MaxDBSizeBytes int64 `json:"maxDBSizeBytes"`
DaemonMode bool `json:"daemonMode"`
TargetNamespaces []string `json:"targetNamespaces"`
AgentImage string `json:"agentImage"`
PullPolicy string `json:"pullPolicy"`
LogLevel logging.Level `json:"logLevel"`
IgnoredUserAgents []string `json:"ignoredUserAgents"`
TapperResources Resources `json:"tapperResources"`
MizuResourcesNamespace string `json:"mizuResourceNamespace"`
MizuApiFilteringOptions api.TrafficFilteringOptions `json:"mizuApiFilteringOptions"`
AgentDatabasePath string `json:"agentDatabasePath"`
Istio bool `json:"istio"`
MaxDBSizeBytes int64 `json:"maxDBSizeBytes"`
AgentImage string `json:"agentImage"`
PullPolicy string `json:"pullPolicy"`
LogLevel logging.Level `json:"logLevel"`
TapperResources Resources `json:"tapperResources"`
MizuResourcesNamespace string `json:"mizuResourceNamespace"`
AgentDatabasePath string `json:"agentDatabasePath"`
}
type WebSocketMessageMetadata struct {
@@ -67,6 +63,11 @@ type WebSocketStatusMessage struct {
TappingStatus []TappedPodStatus `json:"tappingStatus"`
}
type WebSocketTapConfigMessage struct {
*WebSocketMessageMetadata
TapTargets []v1.Pod `json:"pods"`
}
type TapperStatus struct {
TapperName string `json:"tapperName"`
NodeName string `json:"nodeName"`

View File

@@ -11,6 +11,7 @@ package tap
import (
"encoding/json"
"flag"
"fmt"
"os"
"runtime"
"strings"
@@ -60,8 +61,11 @@ type TapOpts struct {
FilterAuthorities []v1.Pod
}
var extensions []*api.Extension // global
var filteringOptions *api.TrafficFilteringOptions // global
var extensions []*api.Extension // global
var filteringOptions *api.TrafficFilteringOptions // global
var tapTargets []v1.Pod // global
var packetSourceManager *source.PacketSourceManager // global
var mainPacketInputChan chan source.TcpPacketInfo // global
func inArrayInt(arr []int, valueToCheck int) bool {
for _, value := range arr {
@@ -86,7 +90,9 @@ func StartPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem,
filteringOptions = options
if opts.FilterAuthorities == nil {
opts.FilterAuthorities = []v1.Pod{}
tapTargets = []v1.Pod{}
} else {
tapTargets = opts.FilterAuthorities
}
if GetMemoryProfilingEnabled() {
@@ -96,6 +102,23 @@ func StartPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem,
go startPassiveTapper(opts, outputItems)
}
func UpdateTapTargets(newTapTargets []v1.Pod) {
tapTargets = newTapTargets
if err := initializePacketSources(); err != nil {
logger.Log.Fatal(err)
}
printNewTapTargets()
}
func printNewTapTargets() {
printStr := ""
for _, tapTarget := range tapTargets {
printStr += fmt.Sprintf("%s (%s), ", tapTarget.Status.PodIP, tapTarget.Name)
}
printStr = strings.TrimRight(printStr, ", ")
logger.Log.Infof("Now tapping: %s", printStr)
}
func printPeriodicStats(cleaner *Cleaner) {
statsPeriod := time.Second * time.Duration(*statsevery)
ticker := time.NewTicker(statsPeriod)
@@ -136,7 +159,11 @@ func printPeriodicStats(cleaner *Cleaner) {
}
}
func initializePacketSources(opts *TapOpts) (*source.PacketSourceManager, error) {
func initializePacketSources() error {
if packetSourceManager != nil {
packetSourceManager.Close()
}
var bpffilter string
if len(flag.Args()) > 0 {
bpffilter = strings.Join(flag.Args(), " ")
@@ -151,7 +178,13 @@ func initializePacketSources(opts *TapOpts) (*source.PacketSourceManager, error)
BpfFilter: bpffilter,
}
return source.NewPacketSourceManager(*procfs, *pids, *fname, *iface, *istio, opts.FilterAuthorities, behaviour)
var err error
if packetSourceManager, err = source.NewPacketSourceManager(*procfs, *pids, *fname, *iface, *istio, tapTargets, behaviour); err != nil {
return err
} else {
packetSourceManager.ReadPackets(!*nodefrag, mainPacketInputChan)
return nil
}
}
func startPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem) {
@@ -161,25 +194,16 @@ func startPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem)
diagnose.InitializeErrorsMap(*debug, *verbose, *quiet)
diagnose.InitializeTapperInternalStats()
sources, err := initializePacketSources(opts)
mainPacketInputChan = make(chan source.TcpPacketInfo)
if err != nil {
if err := initializePacketSources(); err != nil {
logger.Log.Fatal(err)
}
defer sources.Close()
if err != nil {
logger.Log.Fatal(err)
}
packets := make(chan source.TcpPacketInfo)
assembler := NewTcpAssembler(outputItems, streamsMap, opts)
diagnose.AppStats.SetStartTime(time.Now())
sources.ReadPackets(!*nodefrag, packets)
staleConnectionTimeout := time.Second * time.Duration(*staleTimeoutSeconds)
cleaner := Cleaner{
assembler: assembler.Assembler,
@@ -191,7 +215,7 @@ func startPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem)
go printPeriodicStats(&cleaner)
assembler.processPackets(*hexdumppkt, packets)
assembler.processPackets(*hexdumppkt, mainPacketInputChan)
if diagnose.TapErrors.OutputLevel >= 2 {
assembler.dumpStreamPool()

View File

@@ -65,11 +65,11 @@ func (a *tcpAssembler) processPackets(dumpPacket bool, packets <-chan source.Tcp
for packetInfo := range packets {
packetsCount := diagnose.AppStats.IncPacketsCount()
if packetsCount % PACKETS_SEEN_LOG_THRESHOLD == 0 {
if packetsCount%PACKETS_SEEN_LOG_THRESHOLD == 0 {
logger.Log.Debugf("Packets seen: #%d", packetsCount)
}
packet := packetInfo.Packet
data := packet.Data()
diagnose.AppStats.UpdateProcessedBytes(uint64(len(data)))
@@ -91,7 +91,6 @@ func (a *tcpAssembler) processPackets(dumpPacket bool, packets <-chan source.Tcp
CaptureInfo: packet.Metadata().CaptureInfo,
}
diagnose.InternalStats.Totalsz += len(tcp.Payload)
logger.Log.Debugf("%s:%v -> %s:%v", packet.NetworkLayer().NetworkFlow().Src(), tcp.SrcPort, packet.NetworkLayer().NetworkFlow().Dst(), tcp.DstPort)
a.assemblerMutex.Lock()
a.AssembleWithContext(packet.NetworkLayer().NetworkFlow(), tcp, &c)
a.assemblerMutex.Unlock()

View File

@@ -2,7 +2,6 @@ package tap
import (
"encoding/binary"
"fmt"
"sync"
"github.com/google/gopacket"
@@ -75,7 +74,7 @@ func (t *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassem
}
func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
dir, start, end, skip := sg.Info()
dir, _, _, skip := sg.Info()
length, saved := sg.Lengths()
// update stats
sgStats := sg.Stats()
@@ -103,13 +102,6 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
diagnose.InternalStats.OverlapBytes += sgStats.OverlapBytes
diagnose.InternalStats.OverlapPackets += sgStats.OverlapPackets
var ident string
if dir == reassembly.TCPDirClientToServer {
ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir)
} else {
ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir)
}
diagnose.TapErrors.Debug("%s: SG reassembled packet with %d bytes (start:%v,end:%v,skip:%d,saved:%d,nb:%d,%d,overlap:%d,%d)", ident, length, start, end, skip, saved, sgStats.Packets, sgStats.Chunks, sgStats.OverlapBytes, sgStats.OverlapPackets)
if skip == -1 && *allowmissinginit {
// this is allowed
} else if skip != 0 {
@@ -174,7 +166,6 @@ func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
}
func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
diagnose.TapErrors.Debug("%s: Connection closed", t.ident)
if t.isTapTarget && !t.isClosed {
t.Close()
}

View File

@@ -54,7 +54,6 @@ func NewTcpStreamFactory(emitter api.Emitter, streamsMap *tcpStreamMap, opts *Ta
}
func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
logger.Log.Debugf("* NEW: %s %s", net, transport)
fsmOptions := reassembly.TCPSimpleFSMOptions{
SupportMissingEstablishment: *allowmissinginit,
}
@@ -152,22 +151,17 @@ func inArrayPod(pods []v1.Pod, address string) bool {
func (factory *tcpStreamFactory) getStreamProps(srcIP string, srcPort string, dstIP string, dstPort string) *streamProps {
if factory.opts.HostMode {
if inArrayPod(factory.opts.FilterAuthorities, fmt.Sprintf("%s:%s", dstIP, dstPort)) {
logger.Log.Debugf("getStreamProps %s", fmt.Sprintf("+ host1 %s:%s", dstIP, dstPort))
if inArrayPod(tapTargets, fmt.Sprintf("%s:%s", dstIP, dstPort)) {
return &streamProps{isTapTarget: true, isOutgoing: false}
} else if inArrayPod(factory.opts.FilterAuthorities, dstIP) {
logger.Log.Debugf("getStreamProps %s", fmt.Sprintf("+ host2 %s", dstIP))
} else if inArrayPod(tapTargets, dstIP) {
return &streamProps{isTapTarget: true, isOutgoing: false}
} else if inArrayPod(factory.opts.FilterAuthorities, fmt.Sprintf("%s:%s", srcIP, srcPort)) {
logger.Log.Debugf("getStreamProps %s", fmt.Sprintf("+ host3 %s:%s", srcIP, srcPort))
} else if inArrayPod(tapTargets, fmt.Sprintf("%s:%s", srcIP, srcPort)) {
return &streamProps{isTapTarget: true, isOutgoing: true}
} else if inArrayPod(factory.opts.FilterAuthorities, srcIP) {
logger.Log.Debugf("getStreamProps %s", fmt.Sprintf("+ host4 %s", srcIP))
} else if inArrayPod(tapTargets, srcIP) {
return &streamProps{isTapTarget: true, isOutgoing: true}
}
return &streamProps{isTapTarget: false, isOutgoing: false}
} else {
logger.Log.Debugf("getStreamProps %s", fmt.Sprintf("+ notHost3 %s:%s -> %s:%s", srcIP, srcPort, dstIP, dstPort))
return &streamProps{isTapTarget: true}
}
}

66
ui/package-lock.json generated
View File

@@ -7747,9 +7747,9 @@
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ=="
},
"highlight.js": {
"version": "10.7.2",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.2.tgz",
"integrity": "sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg=="
"version": "11.3.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.3.1.tgz",
"integrity": "sha512-PUhCRnPjLtiLHZAQ5A/Dt5F8cWZeMyj9KRsACsWT+OD6OP0x6dp5OmT5jdx0JgEyPxPZZIPQpRN2TciUT7occw=="
},
"hmac-drbg": {
"version": "1.0.1",
@@ -10234,6 +10234,11 @@
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
},
"json-beautify": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/json-beautify/-/json-beautify-1.1.1.tgz",
"integrity": "sha512-17j+Hk2lado0xqKtUcyAjK0AtoHnPSIgktWRsEXgdFQFG9UnaGw6CHa0J7xsvulxRpFl6CrkDFHght1p5ZJc4A=="
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@@ -10612,6 +10617,13 @@
"requires": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"dependencies": {
"highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
}
}
},
"lru-cache": {
@@ -13577,6 +13589,34 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-lowlight": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-lowlight/-/react-lowlight-3.0.0.tgz",
"integrity": "sha512-s0+T81PsCbUZYd/0XrplGc6kQEUdiwLKI0G6umJP1ViqRoZRCvSuHvXOy20Usd2ywDKWLuVETQgBDPeNQhPNZg==",
"requires": {
"lowlight": "^2.4.1"
},
"dependencies": {
"fault": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz",
"integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==",
"requires": {
"format": "^0.2.0"
}
},
"lowlight": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-2.4.1.tgz",
"integrity": "sha512-mQkAG0zGQ9lcYecEft+hl9uV1fD6HpURA83/TYrsxKvb8xX2mfyB+aaV/A/aWmhhEcWVzr9Cc+l/fvUYfEUumw==",
"requires": {
"@types/hast": "^2.0.0",
"fault": "^2.0.0",
"highlight.js": "~11.3.0"
}
}
}
},
"react-refresh": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
@@ -13663,6 +13703,13 @@
"lowlight": "^1.17.0",
"prismjs": "^1.22.0",
"refractor": "^3.2.0"
},
"dependencies": {
"highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
}
}
},
"react-toastify": {
@@ -18149,11 +18196,24 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g=="
},
"xml-formatter": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-2.6.0.tgz",
"integrity": "sha512-+bQeoiE5W3CJdDCHTlveYSWFfQWnYB3uHGeRJ6LlEsL5kT++mWy9iN1cMeEDfBbgOnXO2DNUbmQ6elkR/mCcjg==",
"requires": {
"xml-parser-xo": "^3.2.0"
}
},
"xml-name-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
},
"xml-parser-xo": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-3.2.0.tgz",
"integrity": "sha512-8LRU6cq+d7mVsoDaMhnkkt3CTtAs4153p49fRo+HIB3I1FD1o5CeXRjRH29sQevIfVJIcPjKSsPU/+Ujhq09Rg=="
},
"xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",

View File

@@ -15,6 +15,8 @@
"@types/react-dom": "^17.0.3",
"@uiw/react-textarea-code-editor": "^1.4.12",
"axios": "^0.21.1",
"highlight.js": "^11.3.1",
"json-beautify": "^1.1.1",
"jsonpath": "^1.1.1",
"moment": "^2.29.1",
"node-sass": "^5.0.0",
@@ -23,12 +25,14 @@
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2",
"react-lowlight": "^3.0.0",
"react-scripts": "4.0.3",
"react-scrollable-feed-virtualized": "^1.4.9",
"react-syntax-highlighter": "^15.4.3",
"react-toastify": "^8.0.3",
"typescript": "^4.2.4",
"web-vitals": "^1.1.1"
"web-vitals": "^1.1.1",
"xml-formatter": "^2.6.0"
},
"scripts": {
"start": "react-scripts start",

View File

@@ -23,16 +23,3 @@
font-size: 11px
font-weight: bold
color: $light-blue-color
.httpsDomains
display: none
margin: 0
padding: 0
list-style: none
.customWarningStyle
&:hover
overflow-y: scroll
height: 85px
.httpsDomains
display: block

View File

@@ -1,51 +1,15 @@
import React, {useEffect, useState} from 'react';
import React, {useState} from 'react';
import './App.sass';
import logo from './components/assets/Mizu-logo.svg';
import logo_up9 from './components/assets/logo_up9.svg';
import {Button, Snackbar} from "@material-ui/core";
import {TrafficPage} from "./components/TrafficPage";
import Tooltip from "./components/UI/Tooltip";
import {makeStyles} from "@material-ui/core/styles";
import MuiAlert from '@material-ui/lab/Alert';
import Api from "./helpers/api";
const useStyles = makeStyles(() => ({
tooltip: {
backgroundColor: "#3868dc",
color: "white",
fontSize: 13,
},
}));
const api = new Api();
import {TLSWarning} from "./components/TLSWarning/TLSWarning";
import {Header} from "./components/Header/Header";
const App = () => {
const classes = useStyles();
const [analyzeStatus, setAnalyzeStatus] = useState(null);
const [showTLSWarning, setShowTLSWarning] = useState(false);
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set());
const [statusAuth, setStatusAuth] = useState(null);
useEffect(() => {
(async () => {
try {
const recentTLSLinks = await api.getRecentTLSLinks();
if (recentTLSLinks?.length > 0) {
setAddressesWithTLS(new Set(recentTLSLinks));
setShowTLSWarning(true);
}
const auth = await api.getAuthStatus();
setStatusAuth(auth);
} catch (e) {
console.error(e);
}
})();
}, []);
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set<string>());
const onTLSDetected = (destAddress: string) => {
addressesWithTLS.add(destAddress);
@@ -56,96 +20,16 @@ const App = () => {
}
};
const analysisMessage = analyzeStatus?.isRemoteReady ?
<span>
<table>
<tr>
<td>Status</td>
<td><b>Available</b></td>
</tr>
<tr>
<td>Messages</td>
<td><b>{analyzeStatus?.sentCount}</b></td>
</tr>
</table>
</span> :
analyzeStatus?.sentCount > 0 ?
<span>
<table>
<tr>
<td>Status</td>
<td><b>Processing</b></td>
</tr>
<tr>
<td>Messages</td>
<td><b>{analyzeStatus?.sentCount}</b></td>
</tr>
<tr>
<td colSpan={2}> Please allow a few minutes for the analysis to complete</td>
</tr>
</table>
</span> :
<span>
<table>
<tr>
<td>Status</td>
<td><b>Waiting for traffic</b></td>
</tr>
<tr>
<td>Messages</td>
<td><b>{analyzeStatus?.sentCount}</b></td>
</tr>
</table>
</span>
return (
<div className="mizuApp">
<div className="header">
<div style={{display: "flex", alignItems: "center"}}>
<div className="title"><img src={logo} alt="logo"/></div>
<div className="description">Traffic viewer for Kubernetes</div>
</div>
<div style={{display: "flex", alignItems: "center"}}>
{analyzeStatus?.isAnalyzing &&
<div>
<Tooltip title={analysisMessage} isSimple classes={classes}>
<div>
<Button
style={{fontFamily: "system-ui",
fontWeight: 600,
fontSize: 12,
padding: 8}}
size={"small"}
variant="contained"
color="primary"
startIcon={<img style={{height: 24, maxHeight: "none", maxWidth: "none"}} src={logo_up9} alt={"up9"}/>}
disabled={!analyzeStatus?.isRemoteReady}
onClick={() => {
window.open(analyzeStatus?.remoteUrl)
}}>
Analysis
</Button>
</div>
</Tooltip>
</div>
}
{statusAuth?.email && <div style={{display: "flex", borderLeft: "2px #87878759 solid", paddingLeft: 10, marginLeft: 10}}>
<div style={{color: "rgba(0,0,0,0.75)"}}>
<div style={{fontWeight: 600, fontSize: 13}}>{statusAuth.email}</div>
<div style={{fontSize:11}}>{statusAuth.model}</div>
</div>
</div>}
</div>
</div>
<Header analyzeStatus={analyzeStatus}/>
<TrafficPage setAnalyzeStatus={setAnalyzeStatus} onTLSDetected={onTLSDetected}/>
<Snackbar open={showTLSWarning && !userDismissedTLSWarning}>
<MuiAlert classes={{ filledWarning: 'customWarningStyle' }} elevation={6} variant="filled" onClose={() => setUserDismissedTLSWarning(true)} severity="warning">
Mizu is detecting TLS traffic, this type of traffic will not be displayed.
{addressesWithTLS.size > 0 && <ul className="httpsDomains"> {Array.from(addressesWithTLS, address => <li>{address}</li>)} </ul>}
</MuiAlert>
</Snackbar>
<TLSWarning showTLSWarning={showTLSWarning}
setShowTLSWarning={setShowTLSWarning}
addressesWithTLS={addressesWithTLS}
setAddressesWithTLS={setAddressesWithTLS}
userDismissedTLSWarning={userDismissedTLSWarning}
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>
</div>
);
}

View File

@@ -0,0 +1,86 @@
import {Button} from "@material-ui/core";
import React from "react";
import Tooltip from "../UI/Tooltip";
import logo_up9 from "../assets/logo_up9.svg";
import {makeStyles} from "@material-ui/core/styles";
const useStyles = makeStyles(() => ({
tooltip: {
backgroundColor: "#3868dc",
color: "white",
fontSize: 13,
},
}));
interface AnalyseButtonProps {
analyzeStatus: any
}
export const AnalyzeButton: React.FC<AnalyseButtonProps> = ({analyzeStatus}) => {
const classes = useStyles();
const analysisMessage = analyzeStatus?.isRemoteReady ?
<span>
<table>
<tr>
<td>Status</td>
<td><b>Available</b></td>
</tr>
<tr>
<td>Messages</td>
<td><b>{analyzeStatus?.sentCount}</b></td>
</tr>
</table>
</span> :
analyzeStatus?.sentCount > 0 ?
<span>
<table>
<tr>
<td>Status</td>
<td><b>Processing</b></td>
</tr>
<tr>
<td>Messages</td>
<td><b>{analyzeStatus?.sentCount}</b></td>
</tr>
<tr>
<td colSpan={2}> Please allow a few minutes for the analysis to complete</td>
</tr>
</table>
</span> :
<span>
<table>
<tr>
<td>Status</td>
<td><b>Waiting for traffic</b></td>
</tr>
<tr>
<td>Messages</td>
<td><b>{analyzeStatus?.sentCount}</b></td>
</tr>
</table>
</span>
return ( <div>
<Tooltip title={analysisMessage} isSimple classes={classes}>
<div>
<Button
style={{fontFamily: "system-ui",
fontWeight: 600,
fontSize: 12,
padding: 8}}
size={"small"}
variant="contained"
color="primary"
startIcon={<img style={{height: 24, maxHeight: "none", maxWidth: "none"}} src={logo_up9} alt={"up9"}/>}
disabled={!analyzeStatus?.isRemoteReady}
onClick={() => {
window.open(analyzeStatus?.remoteUrl)
}}>
Analysis
</Button>
</div>
</Tooltip>
</div>);
}

View File

@@ -0,0 +1,13 @@
.authPresentationContainer
display: flex
border-left: 2px #87878759 solid
padding-left: 10px
margin-left: 10px
color: rgba(0,0,0,0.75)
.authEmail
font-weight: 600
font-size: 13px
.authModel
font-size: 11px

View File

@@ -0,0 +1,30 @@
import React, {useEffect, useState} from "react";
import Api from "../../helpers/api";
import './AuthPresentation.sass';
const api = new Api();
export const AuthPresentation = () => {
const [statusAuth, setStatusAuth] = useState(null);
useEffect(() => {
(async () => {
try {
const auth = await api.getAuthStatus();
setStatusAuth(auth);
} catch (e) {
console.error(e);
}
})();
}, []);
return <>
{statusAuth?.email && <div className="authPresentationContainer">
<div>
<div className="authEmail">{statusAuth.email}</div>
<div className="authModel">{statusAuth.model}</div>
</div>
</div>}
</>;
}

View File

@@ -6,16 +6,20 @@ import FancyTextDisplay from "../UI/FancyTextDisplay";
import Queryable from "../UI/Queryable";
import Checkbox from "../UI/Checkbox";
import ProtobufDecoder from "protobuf-decoder";
import {default as jsonBeautify} from "json-beautify";
import {default as xmlBeautify} from "xml-formatter";
interface EntryViewLineProps {
label: string;
value: number | string;
updateQuery: any;
selector: string;
updateQuery?: any;
selector?: string;
overrideQueryValue?: string;
displayIconOnMouseOver?: boolean;
useTooltip?: boolean;
}
const EntryViewLine: React.FC<EntryViewLineProps> = ({label, value, updateQuery, selector, overrideQueryValue}) => {
const EntryViewLine: React.FC<EntryViewLineProps> = ({label, value, updateQuery = null, selector = "", overrideQueryValue = "", displayIconOnMouseOver = true, useTooltip = true}) => {
let query: string;
if (!selector) {
query = "";
@@ -34,7 +38,8 @@ const EntryViewLine: React.FC<EntryViewLineProps> = ({label, value, updateQuery,
style={{float: "right", height: "18px"}}
iconStyle={{marginRight: "20px"}}
flipped={true}
displayIconOnMouseOver={true}
useTooltip={useTooltip}
displayIconOnMouseOver={displayIconOnMouseOver}
>
{label}
</Queryable>
@@ -55,30 +60,47 @@ const EntryViewLine: React.FC<EntryViewLineProps> = ({label, value, updateQuery,
interface EntrySectionCollapsibleTitleProps {
title: string,
color: string,
isExpanded: boolean,
expanded: boolean,
setExpanded: any,
query?: string,
updateQuery?: any,
}
const EntrySectionCollapsibleTitle: React.FC<EntrySectionCollapsibleTitleProps> = ({title, color, isExpanded}) => {
const EntrySectionCollapsibleTitle: React.FC<EntrySectionCollapsibleTitleProps> = ({title, color, expanded, setExpanded, query = "", updateQuery = null}) => {
return <div className={styles.title}>
<div className={`${styles.button} ${isExpanded ? styles.expanded : ''}`} style={{backgroundColor: color}}>
{isExpanded ? '-' : '+'}
<div
className={`${styles.button} ${expanded ? styles.expanded : ''}`}
style={{backgroundColor: color}}
onClick={() => {
setExpanded(!expanded)
}}
>
{expanded ? '-' : '+'}
</div>
<span>{title}</span>
<Queryable
query={query}
updateQuery={updateQuery}
useTooltip={updateQuery ? true : false}
displayIconOnMouseOver={updateQuery ? true : false}
>
<span>{title}</span>
</Queryable>
</div>
}
interface EntrySectionContainerProps {
title: string,
color: string,
query?: string,
updateQuery?: any,
}
export const EntrySectionContainer: React.FC<EntrySectionContainerProps> = ({title, color, children}) => {
export const EntrySectionContainer: React.FC<EntrySectionContainerProps> = ({title, color, children, query = "", updateQuery = null}) => {
const [expanded, setExpanded] = useState(true);
return <CollapsibleContainer
className={styles.collapsibleContainer}
isExpanded={expanded}
onClick={() => setExpanded(!expanded)}
title={<EntrySectionCollapsibleTitle title={title} color={color} isExpanded={expanded}/>}
expanded={expanded}
title={<EntrySectionCollapsibleTitle title={title} color={color} expanded={expanded} setExpanded={setExpanded} query={query} updateQuery={updateQuery}/>}
>
{children}
</CollapsibleContainer>
@@ -101,23 +123,41 @@ export const EntryBodySection: React.FC<EntryBodySectionProps> = ({
contentType,
selector,
}) => {
const MAXIMUM_BYTES_TO_HIGHLIGHT = 10000; // The maximum of chars to highlight in body, in case the response can be megabytes
const supportedLanguages = [['html', 'html'], ['json', 'json'], ['application/grpc', 'json']]; // [[indicator, languageToUse],...]
const jsonLikeFormats = ['json'];
const MAXIMUM_BYTES_TO_FORMAT = 1000000; // The maximum of chars to highlight in body, in case the response can be megabytes
const jsonLikeFormats = ['json', 'yaml', 'yml'];
const xmlLikeFormats = ['xml', 'html'];
const protobufFormats = ['application/grpc'];
const [isWrapped, setIsWrapped] = useState(false);
const supportedFormats = jsonLikeFormats.concat(xmlLikeFormats, protobufFormats);
const formatTextBody = (body): string => {
const chunk = body.slice(0, MAXIMUM_BYTES_TO_HIGHLIGHT);
const bodyBuf = encoding === 'base64' ? atob(chunk) : chunk;
const [isPretty, setIsPretty] = useState(true);
const [showLineNumbers, setShowLineNumbers] = useState(true);
const [decodeBase64, setDecodeBase64] = useState(true);
const isBase64Encoding = encoding === 'base64';
const supportsPrettying = supportedFormats.some(format => contentType?.indexOf(format) > -1);
const formatTextBody = (body: any): string => {
if (!decodeBase64) return body;
const chunk = body.slice(0, MAXIMUM_BYTES_TO_FORMAT);
const bodyBuf = isBase64Encoding ? atob(chunk) : chunk;
if (!isPretty) return bodyBuf;
try {
if (jsonLikeFormats.some(format => contentType?.indexOf(format) > -1)) {
return JSON.stringify(JSON.parse(bodyBuf), null, 2);
return jsonBeautify(JSON.parse(bodyBuf), null, 2, 80);
} else if (xmlLikeFormats.some(format => contentType?.indexOf(format) > -1)) {
return xmlBeautify(bodyBuf, {
indentation: ' ',
filter: (node) => node.type !== 'Comment',
collapseContent: true,
lineSeparator: '\n'
});
} else if (protobufFormats.some(format => contentType?.indexOf(format) > -1)) {
// Replace all non printable characters (ASCII)
const protobufDecoder = new ProtobufDecoder(bodyBuf, true);
return JSON.stringify(protobufDecoder.decode().toSimple(), null, 2);
return jsonBeautify(protobufDecoder.decode().toSimple(), null, 2, 80);
}
} catch (error) {
console.error(error);
@@ -125,33 +165,33 @@ export const EntryBodySection: React.FC<EntryBodySectionProps> = ({
return bodyBuf;
}
const getLanguage = (mimetype) => {
const chunk = content?.slice(0, 100);
if (chunk.indexOf('html') > 0 || chunk.indexOf('HTML') > 0) return supportedLanguages[0][1];
const language = supportedLanguages.find(el => (mimetype + contentType).indexOf(el[0]) > -1);
return language ? language[1] : 'default';
}
return <React.Fragment>
{content && content?.length > 0 && <EntrySectionContainer title='Body' color={color}>
<table>
<tbody>
<EntryViewLine label={'Mime type'} value={contentType} updateQuery={updateQuery} selector={selector} overrideQueryValue={`r".*"`}/>
{encoding && <EntryViewLine label={'Encoding'} value={encoding} updateQuery={updateQuery} selector={selector} overrideQueryValue={`r".*"`}/>}
</tbody>
</table>
{content && content?.length > 0 && <EntrySectionContainer
title='Body'
color={color}
query={`${selector} == r".*"`}
updateQuery={updateQuery}
>
<div style={{display: 'flex', alignItems: 'center', alignContent: 'center', margin: "5px 0"}}>
{supportsPrettying && <div style={{paddingTop: 3}}>
<Checkbox checked={isPretty} onToggle={() => {setIsPretty(!isPretty)}}/>
</div>}
{supportsPrettying && <span style={{marginLeft: '.2rem'}}>Pretty</span>}
<div style={{display: 'flex', alignItems: 'center', alignContent: 'center', margin: "5px 0"}} onClick={() => setIsWrapped(!isWrapped)}>
<div style={{paddingTop: 3}}>
<Checkbox checked={isWrapped} onToggle={() => {}}/>
<div style={{paddingTop: 3, paddingLeft: supportsPrettying ? 20 : 0}}>
<Checkbox checked={showLineNumbers} onToggle={() => {setShowLineNumbers(!showLineNumbers)}}/>
</div>
<span style={{marginLeft: '.5rem'}}>Wrap text</span>
<span style={{marginLeft: '.2rem'}}>Line numbers</span>
{isBase64Encoding && <div style={{paddingTop: 3, paddingLeft: 20}}>
<Checkbox checked={decodeBase64} onToggle={() => {setDecodeBase64(!decodeBase64)}}/>
</div>}
{isBase64Encoding && <span style={{marginLeft: '.2rem'}}>Decode Base64</span>}
</div>
<SyntaxHighlighter
isWrapped={isWrapped}
code={formatTextBody(content)}
language={content?.mimeType ? getLanguage(content.mimeType) : 'default'}
showLineNumbers={showLineNumbers}
/>
</EntrySectionContainer>}
</React.Fragment>
@@ -195,13 +235,20 @@ interface EntryPolicySectionProps {
interface EntryPolicySectionCollapsibleTitleProps {
label: string;
matched: string;
isExpanded: boolean;
expanded: boolean;
setExpanded: any;
}
const EntryPolicySectionCollapsibleTitle: React.FC<EntryPolicySectionCollapsibleTitleProps> = ({label, matched, isExpanded}) => {
const EntryPolicySectionCollapsibleTitle: React.FC<EntryPolicySectionCollapsibleTitleProps> = ({label, matched, expanded, setExpanded}) => {
return <div className={styles.title}>
<span className={`${styles.button} ${isExpanded ? styles.expanded : ''}`}>
{isExpanded ? '-' : '+'}
<span
className={`${styles.button}
${expanded ? styles.expanded : ''}`}
onClick={() => {
setExpanded(!expanded)
}}
>
{expanded ? '-' : '+'}
</span>
<span>
<tr className={styles.dataLine}>
@@ -222,9 +269,8 @@ export const EntryPolicySectionContainer: React.FC<EntryPolicySectionContainerPr
const [expanded, setExpanded] = useState(false);
return <CollapsibleContainer
className={styles.collapsibleContainer}
isExpanded={expanded}
onClick={() => setExpanded(!expanded)}
title={<EntryPolicySectionCollapsibleTitle label={label} matched={matched} isExpanded={expanded}/>}
expanded={expanded}
title={<EntryPolicySectionCollapsibleTitle label={label} matched={matched} expanded={expanded} setExpanded={setExpanded}/>}
>
{children}
</CollapsibleContainer>
@@ -303,7 +349,6 @@ export const EntryContractSection: React.FC<EntryContractSectionProps> = ({color
</EntrySectionContainer>}
{contractContent && <EntrySectionContainer title="Contract" color={color}>
<SyntaxHighlighter
isWrapped={false}
code={contractContent}
language={"yaml"}
/>

View File

@@ -68,6 +68,7 @@
flex-direction: column
overflow: hidden
padding-right: 10px
padding-top: 4px
flex-grow: 1
.separatorRight
@@ -92,3 +93,13 @@
.ip
margin-left: 5px
@media (max-width: 1760px)
.timestamp
display: none
.separatorRight
border-right: 0px
@media (max-width: 1340px)
.separatorRight
display: none

View File

@@ -167,7 +167,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
This is a simple query that matches to HTTP packets with request path "/catalogue":
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`http and request.path == "/catalogue"`}
language="python"
@@ -176,7 +175,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
The same query can be negated for HTTP path and written like this:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`http and request.path != "/catalogue"`}
language="python"
@@ -185,7 +183,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
The syntax supports regular expressions. Here is a query that matches the HTTP requests that send JSON to a server:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`http and request.headers["Accept"] == r"application/json.*"`}
language="python"
@@ -194,7 +191,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
Here is another query that matches HTTP responses with status code 4xx:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`http and response.status == r"4.*"`}
language="python"
@@ -203,7 +199,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
The same exact query can be as integer comparison:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`http and response.status >= 400`}
language="python"
@@ -212,7 +207,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
The results can be queried based on their timestamps:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`timestamp < datetime("10/28/2021, 9:13:02.905 PM")`}
language="python"
@@ -224,7 +218,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
Since Mizu supports various protocols like gRPC, AMQP, Kafka and Redis. It's possible to write complex queries that match multiple protocols like this:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`(http and request.method == "PUT") or (amqp and request.queue.startsWith("test"))\n or (kafka and response.payload.errorCode == 2) or (redis and request.key == "example")\n or (grpc and request.headers[":path"] == r".*foo.*")`}
language="python"
@@ -242,7 +235,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
Such that; clicking this icon in left-pane, would append the query below:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`and dst.name == "carts.sock-shop"`}
language="python"
@@ -260,7 +252,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
A query that compares one selector to another is also a valid query:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`http and (request.query["x"] == response.headers["y"]\n or response.content.text.contains(request.query["x"]))`}
language="python"
@@ -276,7 +267,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
true if the given selector's value starts with the string:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`request.path.startsWith("something")`}
language="python"
@@ -285,7 +275,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
true if the given selector's value ends with the string:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`request.path.endsWith("something")`}
language="python"
@@ -294,7 +283,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
true if the given selector's value contains the string:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`request.path.contains("something")`}
language="python"
@@ -303,7 +291,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
returns the UNIX timestamp which is the equivalent of the time that's provided by the string. Invalid input evaluates to false:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`timestamp >= datetime("10/19/2021, 6:29:02.593 PM")`}
language="python"
@@ -312,7 +299,6 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
limits the number of records that are streamed back as a result of a query. Always evaluates to true:
</Typography>
<SyntaxHighlighter
isWrapped={false}
showLineNumbers={false}
code={`and limit(100)`}
language="python"

View File

@@ -0,0 +1,22 @@
import React from "react";
import {AuthPresentation} from "../AuthPresentation/AuthPresentation";
import {AnalyzeButton} from "../AnalyzeButton/AnalyzeButton";
import logo from '../assets/Mizu-logo.svg';
interface HeaderProps {
analyzeStatus: any
}
export const Header: React.FC<HeaderProps> = ({analyzeStatus}) => {
return <div className="header">
<div style={{display: "flex", alignItems: "center"}}>
<div className="title"><img src={logo} alt="logo"/></div>
<div className="description">Traffic viewer for Kubernetes</div>
</div>
<div style={{display: "flex", alignItems: "center"}}>
{analyzeStatus?.isAnalyzing && <AnalyzeButton analyzeStatus={analyzeStatus}/>}
<AuthPresentation/>
</div>
</div>;
}

View File

@@ -0,0 +1,12 @@
.httpsDomains
display: none
margin: 0
padding: 0
list-style: none
.customWarningStyle
&:hover
overflow-y: scroll
height: 85px
.httpsDomains
display: block

View File

@@ -0,0 +1,42 @@
import {Snackbar} from "@material-ui/core";
import MuiAlert from "@material-ui/lab/Alert";
import React, {useEffect} from "react";
import Api from "../../helpers/api";
import './TLSWarning.sass';
const api = new Api();
interface TLSWarningProps {
showTLSWarning: boolean
setShowTLSWarning: (show: boolean) => void
addressesWithTLS: Set<string>
setAddressesWithTLS: (addresses: Set<string>) => void
userDismissedTLSWarning: boolean
setUserDismissedTLSWarning: (flag: boolean) => void
}
export const TLSWarning: React.FC<TLSWarningProps> = ({showTLSWarning, setShowTLSWarning, addressesWithTLS, setAddressesWithTLS, userDismissedTLSWarning, setUserDismissedTLSWarning}) => {
useEffect(() => {
(async () => {
try {
const recentTLSLinks = await api.getRecentTLSLinks();
if (recentTLSLinks?.length > 0) {
setAddressesWithTLS(new Set(recentTLSLinks));
setShowTLSWarning(true);
}
} catch (e) {
console.error(e);
}
})();
}, []);
return (<Snackbar open={showTLSWarning && !userDismissedTLSWarning}>
<MuiAlert classes={{filledWarning: 'customWarningStyle'}} elevation={6} variant="filled"
onClose={() => setUserDismissedTLSWarning(true)} severity="warning">
Mizu is detecting TLS traffic, this type of traffic will not be displayed.
{addressesWithTLS.size > 0 &&
<ul className="httpsDomains"> {Array.from(addressesWithTLS, address => <li>{address}</li>)} </ul>}
</MuiAlert>
</Snackbar>);
}

View File

@@ -91,7 +91,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
useEffect(() => {
handleQueryChange(query);
}, [query]);
}, [query, handleQueryChange]);
useEffect(() => {
if (query) {

View File

@@ -1,38 +1,25 @@
import React, {useState} from "react";
import React from "react";
import collapsedImg from "../assets/collapsed.svg";
import expandedImg from "../assets/expanded.svg";
import "./style/CollapsibleContainer.sass";
interface Props {
title: string | React.ReactNode,
onClick?: (e: React.SyntheticEvent) => void,
isExpanded?: boolean,
expanded: boolean,
titleClassName?: string,
stickyHeader?: boolean,
className?: string,
initialExpanded?: boolean;
passiveOnClick?: boolean; //whether specifying onClick overrides internal _isExpanded state handling
stickyHeader?: boolean,
}
const CollapsibleContainer: React.FC<Props> = ({title, children, isExpanded, onClick, titleClassName, stickyHeader = false, className, initialExpanded = true, passiveOnClick}) => {
const [_isExpanded, _setExpanded] = useState(initialExpanded);
let expanded = isExpanded !== undefined ? isExpanded : _isExpanded;
const CollapsibleContainer: React.FC<Props> = ({title, children, expanded, titleClassName, className, stickyHeader = false}) => {
const classNames = `CollapsibleContainer ${expanded ? "CollapsibleContainer-Expanded" : "CollapsibleContainer-Collapsed"} ${className ? className : ''}`;
// This is needed to achieve the sticky header feature.
// This is needed to achieve the sticky header feature.
// It is needed an un-contained component for the css to work properly.
const content = <React.Fragment>
<div
className={`CollapsibleContainer-Header ${stickyHeader ? "CollapsibleContainer-Header-Sticky" : ""}
className={`CollapsibleContainer-Header ${stickyHeader ? "CollapsibleContainer-Header-Sticky" : ""}
${expanded ? "CollapsibleContainer-Header-Expanded" : ""}`}
onClick={(e) => {
if (onClick) {
onClick(e)
if (passiveOnClick !== true)
return;
}
_setExpanded(!_isExpanded)
}}
>
{
React.isValidElement(title)?

View File

@@ -1,152 +0,0 @@
export const highlighterStyle = {
"code[class*=\"language-\"]": {
"color": "#494677",
"fontFamily": "Inconsolata, Monaco, Consolas, 'Courier New', Courier, monospace",
"direction": "ltr",
"textAlign": "left",
"whiteSpace": "pre",
"wordSpacing": "normal",
"wordBreak": "normal",
"lineHeight": "1.5",
"MozTabSize": "4",
"OTabSize": "4",
"tabSize": "4",
"padding": "1rem",
"WebkitHyphetokenns": "none",
"MozHyphens": "none",
"msHyphens": "none",
"hyphens": "none"
},
"pre[class*=\"language-\"]": {
"color": "#494677",
"fontFamily": "Inconsolata, Monaco, Consolas, 'Courier New', Courier, monospace",
"direction": "ltr",
"textAlign": "left",
"whiteSpace": "pre",
"wordSpacing": "normal",
"wordBreak": "normal",
"lineHeight": "1.2",
"MozTabSize": "4",
"OTabSize": "4",
"tabSize": "4",
"WebkitHyphens": "none",
"MozHyphens": "none",
"msHyphens": "none",
"hyphens": "none",
"padding": "0",
"margin": ".5em 0",
"overflow": "auto",
"borderRadius": "0.3em",
"background": "#F7F9FC"
},
":not(pre) > code[class*=\"language-\"]": {
"background": "#F7F9FC",
"padding": ".1em",
"borderRadius": ".3em"
},
"comment": {
"color": "#5d6aa0"
},
"prolog": {
"color": "#494677"
},
"doctype": {
"color": "#494677"
},
"cdata": {
"color": "#494677"
},
"punctuation": {
"color": "#494677"
},
".namespace": {
"Opacity": ".7"
},
"property": {
"color": "#627ef7"
},
"keyword": {
"color": "#627ef7"
},
"tag": {
"color": "#627ef7"
},
"class-name": {
"color": "#3eb545",
"textDecoration": "underline"
},
"boolean": {
"color": "#3eb545"
},
"constant": {
"color": "#3eb545"
},
"symbol": {
"color": "#ff3a30"
},
"deleted": {
"color": "#ff3a30"
},
"number": {
"color": "#ff16f7"
},
"selector": {
"color": "rgb(9,224,19)"
},
"attr-name": {
"color": "rgb(9,224,19)"
},
"string": {
"color": "rgb(9,224,19)"
},
"char": {
"color": "rgb(9,224,19)"
},
"builtin": {
"color": "rgb(9,224,19)"
},
"inserted": {
"color": "rgb(9,224,19)"
},
"variable": {
"color": "#C6C5FE"
},
"operator": {
"color": "#A1A1A1"
},
"entity": {
"color": "#fdab2b",
"cursor": "help"
},
"url": {
"color": "#96CBFE"
},
".language-css .token.string": {
"color": "#87C38A"
},
".style .token.string": {
"color": "#87C38A"
},
"atrule": {
"color": "#fdab2b"
},
"attr-value": {
"color": "#f8c575"
},
"function": {
"color": "#fdab2b"
},
"regex": {
"color": "#fab248"
},
"important": {
"color": "#fd971f",
"fontWeight": "bold"
},
"bold": {
"fontWeight": "bold"
},
"italic": {
"fontStyle": "italic"
}
};

View File

@@ -26,12 +26,24 @@
}
}
.wrapped{
pre {
code {
&:last-child {
white-space: pre-wrap!important
}
}
}
}
code.hljs {
white-space: pre-wrap;
}
code.hljs:before {
counter-reset: listing;
}
code.hljs .hljs-marker-line {
counter-increment: listing;
}
code.hljs .hljs-marker-line:before {
content: counter(listing) " ";
display: inline-block;
width: 3rem;
padding-left: auto;
margin-left: auto;
text-align: right;
opacity: .5;
}

View File

@@ -1,30 +1,47 @@
import React from 'react';
import {Prism as SyntaxHighlighterContainer} from 'react-syntax-highlighter';
import {highlighterStyle} from './highlighterStyle'
import Lowlight from 'react-lowlight'
import 'highlight.js/styles/atom-one-light.css'
import './index.scss';
import xml from 'highlight.js/lib/languages/xml'
import json from 'highlight.js/lib/languages/json'
import protobuf from 'highlight.js/lib/languages/protobuf'
import javascript from 'highlight.js/lib/languages/javascript'
import actionscript from 'highlight.js/lib/languages/actionscript'
import wasm from 'highlight.js/lib/languages/wasm'
import handlebars from 'highlight.js/lib/languages/handlebars'
import yaml from 'highlight.js/lib/languages/yaml'
import python from 'highlight.js/lib/languages/python'
Lowlight.registerLanguage('python', python);
Lowlight.registerLanguage('xml', xml);
Lowlight.registerLanguage('json', json);
Lowlight.registerLanguage('yaml', yaml);
Lowlight.registerLanguage('protobuf', protobuf);
Lowlight.registerLanguage('javascript', javascript);
Lowlight.registerLanguage('actionscript', actionscript);
Lowlight.registerLanguage('wasm', wasm);
Lowlight.registerLanguage('handlebars', handlebars);
interface Props {
code: string;
style?: any;
showLineNumbers?: boolean;
className?: string;
language?: string;
isWrapped?: boolean;
}
export const SyntaxHighlighter: React.FC<Props> = ({
code,
style = highlighterStyle,
showLineNumbers = true,
className,
language = 'python',
isWrapped = false,
}) => {
return <div className={`highlighterContainer ${className ? className : ''} ${isWrapped ? 'wrapped' : ''}`}>
<SyntaxHighlighterContainer language={language} style={style} showLineNumbers={showLineNumbers}>
{code ?? ""}
</SyntaxHighlighterContainer>
</div>;
code,
showLineNumbers = false,
language = null
}) => {
const markers = showLineNumbers ? code.split("\n").map((item, i) => {
return {
line: i + 1,
className: 'hljs-marker-line'
}
}) : [];
return <div style={{fontSize: ".75rem"}}><Lowlight language={language ? language : ""} value={code} markers={markers}/></div>;
};
export default SyntaxHighlighter;