Compare commits

...

59 Commits
0.3.0 ... 0.0.4

Author SHA1 Message Date
Alex Haiut
e6f140adb2 use separate checksum files 2021-06-20 14:24:03 +03:00
Alex Haiut
1af2cd728d using markdown for release text 2021-06-20 14:08:03 +03:00
Alex Haiut
d5f6093084 fixed build error - created bin directory upfront 2021-06-20 13:56:07 +03:00
Alex Haiut
32ad2f17c3 added checksum calc to CLI makefile 2021-06-20 13:52:10 +03:00
Roee Gadot
553433622e no message 2021-06-20 10:42:27 +03:00
Roee Gadot
b6917ed0b7 no message 2021-06-20 08:53:12 +03:00
Roee Gadot
d22e5a9620 #minor 2021-06-20 08:24:04 +03:00
Roee Gadot
e880a9224f no message 2021-06-20 08:17:48 +03:00
Roee Gadot
67c3e5aab5 no message 2021-06-19 10:06:20 +03:00
Roee Gadot
ed791c988e no message 2021-06-19 10:00:22 +03:00
Roee Gadot
3567f5c00b no message 2021-06-19 09:40:19 +03:00
Roee Gadot
605c67c22f no message 2021-06-17 16:57:30 +03:00
Roee Gadot
9b44838fed no message 2021-06-17 15:59:43 +03:00
Roee Gadot
9f6abf7a32 no message 2021-06-17 11:23:05 +03:00
gadotroee
609f85e6ae Update tag-temp.yaml 2021-06-15 09:33:47 +03:00
gadotroee
d2188bfc22 Update tag-temp.yaml 2021-06-15 09:12:58 +03:00
gadotroee
d5942c3c49 Create tag-temp.yaml 2021-06-15 08:59:28 +03:00
Roee Gadot
f03c2adce4 remove main.yml and fix branches 2021-06-13 08:38:20 +03:00
Roee Gadot
93ebec09b9 no message 2021-06-10 21:41:20 +03:00
Roee Gadot
06c7b9c748 no message 2021-06-10 21:36:44 +03:00
Roee Gadot
db66119182 missing ) 2021-06-10 21:33:22 +03:00
Roee Gadot
2ae6153631 no message 2021-06-10 21:32:17 +03:00
Roee Gadot
2dfb0b2549 no message 2021-06-10 21:23:40 +03:00
Roee Gadot
a2b774ae1f no message 2021-06-10 21:21:11 +03:00
Roee Gadot
862f4252c3 yaml error 2021-06-10 21:17:07 +03:00
Roee Gadot
d5f057156e no message 2021-06-10 21:16:06 +03:00
Roee Gadot
96ce694945 trying new approach 2021-06-10 21:13:15 +03:00
gadotroee
2d7ff95426 Update main.yml 2021-06-08 15:17:15 +03:00
gadotroee
af9daf91cf Update main.yml 2021-06-08 15:08:55 +03:00
gadotroee
ea82d1779b Update main.yml 2021-06-08 15:05:55 +03:00
gadotroee
51ddbf3815 Update main.yml 2021-06-08 15:01:23 +03:00
gadotroee
8bbdcb6877 Create main.yml 2021-06-08 14:54:13 +03:00
gadotroee
31dcfc4b2e TRA-3318 - Cookies not null and fix har file names (#69)
* no message
2021-06-08 11:17:02 +03:00
gadotroee
fcf27e7c4d String and not pointers (#68) 2021-06-07 15:19:12 +03:00
RamiBerm
8b4d813bd8 TRA-3311 validate xml before parsing
TRA-3311 validate xml before parsing
2021-06-07 11:43:04 +03:00
RamiBerm
b7d3ff6eb8 Update main.go 2021-06-07 11:35:50 +03:00
RamiBerm
931b6f4260 Update main.go and messageSensitiveDataCleaner.go 2021-06-07 11:35:12 +03:00
Igor Gov
ba7b97cf7b Merge pull request #66 from up9inc/mizu_view
TRA-3235 Implementation of Mizu view command
2021-06-06 17:21:21 +03:00
Igor Gov
8316f8456f . 2021-06-06 17:18:58 +03:00
Igor Gov
f98185f0f0 . 2021-06-06 17:01:45 +03:00
Igor Gov
88a5befd4b Implementation of Mizu view command 2021-06-06 17:00:23 +03:00
nimrod-up9
1bf5bf0b31 TRA-3299 Reduce footprint and Add Tolerances(#65)
* Use lib const for DNSClusterFirstWithHostNet.

* Whitespace.

* Break lines.

* Added affinity to pod names.

* Added tolerations to NoExecute and NoSchedule taints.
2021-06-03 19:48:12 +03:00
gadotroee
2c8d1f854d TRA-3234 fetch with _source + no hard limit (#64)
* remove the HARD limit of 5000
2021-06-02 08:17:45 +03:00
RamiBerm
7dad5be676 TRA-3278 data masking 2nd step
TRA-3278 data masking 2nd step
2021-06-01 17:48:28 +03:00
RamiBerm
b3cfd20a78 Update tap.go 2021-06-01 17:18:57 +03:00
RamiBerm
1c4588a83c Update serializableRegexp.go 2021-06-01 14:41:42 +03:00
RamiBerm
76bb3db553 Update provider.go 2021-06-01 14:40:34 +03:00
RamiBerm
ff2131ea1e Update consts.go, messageSensitiveDataCleaner.go, and tap.go 2021-06-01 14:39:22 +03:00
RamiBerm
107c2d5b59 Update main.go, messageSensitiveDataCleaner.go, and 3 more files... 2021-06-01 14:27:19 +03:00
RamiBerm
4bc16fa0b4 Update main.go, messageSensitiveDataCleaner.go, and 6 more files... 2021-06-01 14:25:52 +03:00
RamiBerm
47237f05a5 WIP 2021-05-31 17:53:21 +03:00
RamiBerm
ea8359cbdf TRA-3278 sensitive data masking
TRA-3278 sensitive data masking
2021-05-31 13:00:41 +03:00
RamiBerm
27c7d66478 Update main.go, consts.go, and 3 more files... 2021-05-31 09:58:12 +03:00
RamiBerm
5473f11215 Update messageSensitiveDataCleaner.go 2021-05-30 15:54:45 +03:00
RamiBerm
3497dc057b Update consts.go and messageSensitiveDataCleaner.go 2021-05-30 15:54:07 +03:00
RamiBerm
f958de6619 Update messageSensitiveDataCleaner.go 2021-05-30 15:49:02 +03:00
RamiBerm
19fba89ca5 Update main.go, consts.go, and 2 more files... 2021-05-30 15:21:39 +03:00
RamiBerm
07c19b5d6d WIP 2021-05-27 18:17:26 +03:00
lirazyehezkel
fc5d6b2d0a Show pod name and namespace (#61) 2021-05-27 13:48:37 +03:00
26 changed files with 632 additions and 146 deletions

View File

@@ -2,8 +2,9 @@ name: public-cli
on:
push:
branches:
- 'develop'
- 'main'
- develop
- main
- my-temp-release-check
jobs:
docker:
runs-on: ubuntu-latest
@@ -15,5 +16,32 @@ jobs:
with:
service_account_key: ${{ secrets.GCR_JSON_KEY }}
export_default_credentials: true
- uses: haya14busa/action-cond@v1
id: condval
with:
cond: ${{ github.ref == 'refs/heads/main' }}
if_true: "minor"
if_false: "patch"
- name: Auto Increment Semver Action
uses: MCKanpolat/auto-semver-action@1.0.5
id: versioning
with:
releaseType: ${{ steps.condval.outputs.value }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Get base image name
shell: bash
run: |
echo "##[set-output name=build_timestamp;]$(echo $(date +%s))"
echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: version_parameters
- name: Build and Push CLI
run: make push-cli
run: make push-cli SEM_VER='${{ steps.versioning.outputs.version }}' BUILD_TIMESTAMP='${{ steps.version_parameters.outputs.build_timestamp }}'
- name: publish
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "cli/bin/*"
commit: ${{ steps.version_parameters.outputs.branch }}
tag: ${{ steps.versioning.outputs.version }}
prerelease: ${{ github.ref != 'refs/heads/main' }}
bodyFile: 'cli/bin/checksums.md'

View File

@@ -4,6 +4,7 @@ go 1.16
require (
github.com/antoniodipinto/ikisocket v0.0.0-20210417133349-f1502512d69a
github.com/beevik/etree v1.1.0
github.com/djherbis/atime v1.0.0
github.com/fasthttp/websocket v1.4.3-beta.1 // indirect
github.com/go-playground/locales v0.13.0

View File

@@ -48,6 +48,8 @@ github.com/antoniodipinto/ikisocket v0.0.0-20210417133349-f1502512d69a h1:76llBl
github.com/antoniodipinto/ikisocket v0.0.0-20210417133349-f1502512d69a/go.mod h1:QvDfsDQDmGxUsvEeWabVZ5pp2FMXpOkwQV0L6SE6cp0=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=

View File

@@ -11,6 +11,7 @@ import (
"mizuserver/pkg/middleware"
"mizuserver/pkg/models"
"mizuserver/pkg/routes"
"mizuserver/pkg/sensitiveDataFiltering"
"mizuserver/pkg/tap"
"mizuserver/pkg/utils"
"os"
@@ -22,8 +23,6 @@ var aggregator = flag.Bool("aggregator", false, "Run in aggregator mode with API
var standalone = flag.Bool("standalone", false, "Run in standalone tapper and API mode")
var aggregatorAddress = flag.String("aggregator-address", "", "Address of mizu collector for tapping")
const nodeNameEnvVar = "NODE_NAME"
const tappedAddressesPerNodeDictEnvVar = "TAPPED_ADDRESSES_PER_HOST"
func main() {
flag.Parse()
@@ -34,7 +33,9 @@ func main() {
if *standalone {
harOutputChannel := tap.StartPassiveTapper()
go api.StartReadingEntries(harOutputChannel, tap.HarOutputDir)
filteredHarChannel := make(chan *tap.OutputChannelItem)
go filterHarHeaders(harOutputChannel, filteredHarChannel, getTrafficFilteringOptions())
go api.StartReadingEntries(filteredHarChannel, nil)
hostApi(nil)
} else if *shouldTap {
if *aggregatorAddress == "" {
@@ -55,7 +56,9 @@ func main() {
go pipeChannelToSocket(socketConnection, harOutputChannel)
} else if *aggregator {
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
go api.StartReadingEntries(socketHarOutChannel, nil)
filteredHarChannel := make(chan *tap.OutputChannelItem)
go api.StartReadingEntries(filteredHarChannel, nil)
go filterHarHeaders(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
hostApi(socketHarOutChannel)
}
@@ -89,15 +92,36 @@ func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
func getTapTargets() []string {
nodeName := os.Getenv(nodeNameEnvVar)
nodeName := os.Getenv(shared.NodeNameEnvVar)
var tappedAddressesPerNodeDict map[string][]string
err := json.Unmarshal([]byte(os.Getenv(tappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
err := json.Unmarshal([]byte(os.Getenv(shared.TappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
if err != nil {
panic(fmt.Sprintf("env var value of %s is invalid! must be map[string][]string %v", tappedAddressesPerNodeDict, err))
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", shared.TappedAddressesPerNodeDictEnvVar, tappedAddressesPerNodeDict, err))
}
return tappedAddressesPerNodeDict[nodeName]
}
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
if filteringOptionsJson == "" {
return nil
}
var filteringOptions shared.TrafficFilteringOptions
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
if err != nil {
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the shared.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
}
return &filteringOptions
}
func filterHarHeaders(inChannel <- chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
for message := range inChannel {
sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
outChannel <- message
}
}
func pipeChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tap.OutputChannelItem) {
if connection == nil {
panic("Websocket connection is nil")

View File

@@ -97,8 +97,8 @@ func saveHarToDb(entry *har.Entry, sender string) {
serviceName, urlPath, serviceHostName := getServiceNameFromUrl(entry.Request.URL)
entryId := primitive.NewObjectID().Hex()
var (
resolvedSource *string
resolvedDestination *string
resolvedSource string
resolvedDestination string
)
if k8sResolver != nil {
resolvedSource = k8sResolver.Resolve(sender)

View File

@@ -64,7 +64,7 @@ func GetEntries(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(baseEntries)
}
func GetHAR(c *fiber.Ctx) error {
func GetHARs(c *fiber.Ctx) error {
entriesFilter := &models.HarFetchRequestBody{}
order := OrderDesc
if err := c.QueryParser(entriesFilter); err != nil {
@@ -87,26 +87,28 @@ func GetHAR(c *fiber.Ctx) error {
utils.ReverseSlice(entries)
}
harsObject := map[string]*har.HAR{}
harsObject := map[string]*models.ExtendedHAR{}
for _, entryData := range entries {
harEntryObject := []byte(entryData.Entry)
var harEntry har.Entry
_ = json.Unmarshal(harEntryObject, &harEntry)
_ = json.Unmarshal([]byte(entryData.Entry), &harEntry)
sourceOfEntry := *entryData.ResolvedSource
if harOfSource, ok := harsObject[sourceOfEntry]; ok {
sourceOfEntry := entryData.ResolvedSource
fileName := fmt.Sprintf("%s.har", sourceOfEntry)
if harOfSource, ok := harsObject[fileName]; ok {
harOfSource.Log.Entries = append(harOfSource.Log.Entries, &harEntry)
} else {
var entriesHar []*har.Entry
entriesHar = append(entriesHar, &harEntry)
harsObject[sourceOfEntry] = &har.HAR{
Log: &har.Log{
harsObject[fileName] = &models.ExtendedHAR{
Log: &models.ExtendedLog{
Version: "1.2",
Creator: &har.Creator{
Name: "mizu",
Version: "0.0.1",
Creator: &models.ExtendedCreator{
Creator: &har.Creator{
Name: "mizu",
Version: "0.0.2",
},
Source: sourceOfEntry,
},
Entries: entriesHar,
},
@@ -134,8 +136,8 @@ func GetEntry(c *fiber.Ctx) error {
unmarshallErr := json.Unmarshal([]byte(entryData.Entry), &fullEntry)
utils.CheckErr(unmarshallErr)
if entryData.ResolvedDestination != nil {
fullEntry.Request.URL = utils.SetHostname(fullEntry.Request.URL, *entryData.ResolvedDestination)
if entryData.ResolvedDestination != "" {
fullEntry.Request.URL = utils.SetHostname(fullEntry.Request.URL, entryData.ResolvedDestination)
}
return c.Status(fiber.StatusOK).JSON(fullEntry)

View File

@@ -2,6 +2,7 @@ package models
import (
"encoding/json"
"github.com/google/martian/har"
"github.com/up9inc/mizu/shared"
"mizuserver/pkg/tap"
"time"
@@ -20,8 +21,8 @@ type MizuEntry struct {
Service string `json:"service" gorm:"column:service"`
Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
Path string `json:"path" gorm:"column:path"`
ResolvedSource *string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
ResolvedDestination *string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
ResolvedSource string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
}
type BaseEntryDetails struct {
@@ -37,7 +38,7 @@ type BaseEntryDetails struct {
type EntryData struct {
Entry string `json:"entry,omitempty"`
ResolvedDestination *string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
}
type EntriesFilter struct {
@@ -47,7 +48,7 @@ type EntriesFilter struct {
}
type HarFetchRequestBody struct {
Limit int `query:"limit" validate:"max=5000"`
Limit int `query:"limit"`
}
type WebSocketEntryMessage struct {
@@ -80,3 +81,24 @@ func CreateWebsocketTappedEntryMessage(base *tap.OutputChannelItem) ([]byte, err
}
return json.Marshal(message)
}
// ExtendedHAR is the top level object of a HAR log.
type ExtendedHAR struct {
Log *ExtendedLog `json:"log"`
}
// ExtendedLog is the HAR HTTP request and response log.
type ExtendedLog struct {
// Version number of the HAR format.
Version string `json:"version"`
// Creator holds information about the log creator application.
Creator *ExtendedCreator `json:"creator"`
// Entries is a list containing requests and responses.
Entries []*har.Entry `json:"entries"`
}
type ExtendedCreator struct {
*har.Creator
Source string `json:"_source"`
}

View File

@@ -33,12 +33,12 @@ func (resolver *Resolver) Start(ctx context.Context) {
}
}
func (resolver *Resolver) Resolve(name string) *string {
func (resolver *Resolver) Resolve(name string) string {
resolvedName, isFound := resolver.nameMap[name]
if !isFound {
return nil
return ""
}
return &resolvedName
return resolvedName
}
func (resolver *Resolver) watchPods(ctx context.Context) error {

View File

@@ -12,7 +12,7 @@ func EntriesRoutes(fiberApp *fiber.App) {
routeGroup.Get("/entries", controllers.GetEntries) // get entries (base/thin entries)
routeGroup.Get("/entries/:entryId", controllers.GetEntry) // get single (full) entry
routeGroup.Get("/har", controllers.GetHAR)
routeGroup.Get("/har", controllers.GetHARs)
routeGroup.Get("/resetDB", controllers.DeleteAllEntries) // get single (full) entry
routeGroup.Get("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB

View File

@@ -0,0 +1,10 @@
package sensitiveDataFiltering
const maskedFieldPlaceholderValue = "[REDACTED]"
//these values MUST be all lower case and contain no `-` or `_` characters
var personallyIdentifiableDataFields = []string{"token", "authorization", "authentication", "cookie", "userid", "password",
"username", "user", "key", "passcode", "pass", "auth", "authtoken", "jwt",
"bearer", "clientid", "clientsecret", "redirecturi", "phonenumber",
"zip", "zipcode", "address", "country", "firstname", "lastname",
"middlename", "fname", "lname", "birthdate"}

View File

@@ -0,0 +1,191 @@
package sensitiveDataFiltering
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"mizuserver/pkg/tap"
"net/url"
"strings"
"github.com/beevik/etree"
"github.com/google/martian/har"
"github.com/up9inc/mizu/shared"
)
func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, options *shared.TrafficFilteringOptions) {
filterHarHeaders(harOutputItem.HarEntry.Request.Headers)
filterHarHeaders(harOutputItem.HarEntry.Response.Headers)
harOutputItem.HarEntry.Request.Cookies = make([]har.Cookie, 0, 0)
harOutputItem.HarEntry.Response.Cookies = make([]har.Cookie, 0, 0)
harOutputItem.HarEntry.Request.URL = filterUrl(harOutputItem.HarEntry.Request.URL)
for i, queryString := range harOutputItem.HarEntry.Request.QueryString {
if isFieldNameSensitive(queryString.Name) {
harOutputItem.HarEntry.Request.QueryString[i].Value = maskedFieldPlaceholderValue
}
}
if harOutputItem.HarEntry.Request.PostData != nil {
requestContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Request.Headers)
filteredRequestBody, err := filterHttpBody([]byte(harOutputItem.HarEntry.Request.PostData.Text), requestContentType, options)
if err == nil {
harOutputItem.HarEntry.Request.PostData.Text = string(filteredRequestBody)
}
}
if harOutputItem.HarEntry.Response.Content != nil {
responseContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Response.Headers)
filteredResponseBody, err := filterHttpBody(harOutputItem.HarEntry.Response.Content.Text, responseContentType, options)
if err == nil {
harOutputItem.HarEntry.Response.Content.Text = filteredResponseBody
}
}
}
func filterHarHeaders(headers []har.Header) {
for i, header := range headers {
if isFieldNameSensitive(header.Name) {
headers[i].Value = maskedFieldPlaceholderValue
}
}
}
func getContentTypeHeaderValue(headers []har.Header) string {
for _, header := range headers {
if strings.ToLower(header.Name) == "content-type" {
return header.Value
}
}
return ""
}
func isFieldNameSensitive(fieldName string) bool {
name := strings.ToLower(fieldName)
name = strings.ReplaceAll(name, "_", "")
name = strings.ReplaceAll(name, "-", "")
name = strings.ReplaceAll(name, " ", "")
for _, sensitiveField := range personallyIdentifiableDataFields {
if strings.Contains(name, sensitiveField) {
return true
}
}
return false
}
func filterHttpBody(bytes []byte, contentType string, options *shared.TrafficFilteringOptions) ([]byte, error) {
mimeType := strings.Split(contentType, ";")[0]
switch strings.ToLower(mimeType) {
case "application/json":
return filterJsonBody(bytes)
case "text/html":
fallthrough
case "application/xhtml+xml":
fallthrough
case "text/xml":
fallthrough
case "application/xml":
return filterXmlEtree(bytes)
case "text/plain":
if options != nil && options.PlainTextMaskingRegexes != nil {
return filterPlainText(bytes, options), nil
}
}
return bytes, nil
}
func filterPlainText(bytes []byte, options *shared.TrafficFilteringOptions) []byte {
for _, regex := range options.PlainTextMaskingRegexes {
bytes = regex.ReplaceAll(bytes, []byte(maskedFieldPlaceholderValue))
}
return bytes
}
func filterXmlEtree(bytes []byte) ([]byte, error) {
if !IsValidXML(bytes) {
return nil, errors.New("Invalid XML")
}
xmlDoc := etree.NewDocument()
err := xmlDoc.ReadFromBytes(bytes)
if err != nil {
return nil, err
} else {
filterXmlElement(xmlDoc.Root())
}
return xmlDoc.WriteToBytes()
}
func IsValidXML(data []byte) bool {
return xml.Unmarshal(data, new(interface{})) == nil
}
func filterXmlElement(element *etree.Element) {
for i, attribute := range element.Attr {
if isFieldNameSensitive(attribute.Key) {
element.Attr[i].Value = maskedFieldPlaceholderValue
}
}
if element.ChildElements() == nil || len(element.ChildElements()) == 0 {
if isFieldNameSensitive(element.Tag) {
element.SetText(maskedFieldPlaceholderValue)
}
} else {
for _, element := range element.ChildElements() {
filterXmlElement(element)
}
}
}
func filterJsonBody(bytes []byte) ([]byte, error) {
var bodyJsonMap map[string] interface{}
err := json.Unmarshal(bytes ,&bodyJsonMap)
if err != nil {
return nil, err
}
filterJsonMap(bodyJsonMap)
return json.Marshal(bodyJsonMap)
}
func filterJsonMap(jsonMap map[string] interface{}) {
for key, value := range jsonMap {
if value == nil {
return
}
nestedMap, isNested := value.(map[string] interface{})
if isNested {
filterJsonMap(nestedMap)
} else {
if isFieldNameSensitive(key) {
jsonMap[key] = maskedFieldPlaceholderValue
}
}
}
}
// receives string representing url, returns string url without sensitive query param values (http://service/api?userId=bob&password=123&type=login -> http://service/api?userId=[REDACTED]&password=[REDACTED]&type=login)
func filterUrl(originalUrl string) string {
parsedUrl, err := url.Parse(originalUrl)
if err != nil {
return fmt.Sprintf("http://%s", maskedFieldPlaceholderValue)
} else {
if len(parsedUrl.RawQuery) > 0 {
newQueryArgs := make([]string, 0)
for urlQueryParamName, urlQueryParamValues := range parsedUrl.Query() {
newValues := urlQueryParamValues
if isFieldNameSensitive(urlQueryParamName) {
newValues = []string {maskedFieldPlaceholderValue}
}
for _, paramValue := range newValues {
newQueryArgs = append(newQueryArgs, fmt.Sprintf("%s=%s", urlQueryParamName, paramValue))
}
}
parsedUrl.RawQuery = strings.Join(newQueryArgs, "&")
}
return parsedUrl.String()
}
}

View File

@@ -13,6 +13,7 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/up9inc/mizu/shared"
"log"
"os"
"os/signal"
@@ -34,7 +35,6 @@ import (
const AppPortsEnvVar = "APP_PORTS"
const OutPortEnvVar = "WEB_SOCKET_PORT"
const maxHTTP2DataLenEnvVar = "HTTP2_DATA_SIZE_LIMIT"
const hostModeEnvVar = "HOST_MODE"
// default is 1MB, more than the max size accepted by collector and traffic-dumper
const maxHTTP2DataLenDefault = 1 * 1024 * 1024
const cleanPeriod = time.Second * 10
@@ -258,7 +258,7 @@ func startPassiveTapper(harWriter *HarWriter) {
maxHTTP2DataLen = convertedInt
}
}
hostMode = os.Getenv(hostModeEnvVar) == "1"
hostMode = os.Getenv(shared.HostModeEnvVar) == "1"
fmt.Printf("App Ports: %v\n", appPorts)
fmt.Printf("Tap output websocket port: %s\n", tapOutputPort)

View File

@@ -65,9 +65,9 @@ func SetHostname(address, newHostname string) string {
func GetResolvedBaseEntry(entry models.MizuEntry) models.BaseEntryDetails {
entryUrl := entry.Url
service := entry.Service
if entry.ResolvedDestination != nil {
entryUrl = SetHostname(entryUrl, *entry.ResolvedDestination)
service = SetHostname(service, *entry.ResolvedDestination)
if entry.ResolvedDestination != "" {
entryUrl = SetHostname(entryUrl, entry.ResolvedDestination)
service = SetHostname(service, entry.ResolvedDestination)
}
return models.BaseEntryDetails{
Id: entry.EntryId,
@@ -84,4 +84,4 @@ func GetResolvedBaseEntry(entry models.MizuEntry) models.BaseEntryDetails {
func GetBytesFromStruct(v interface{}) []byte{
a, _ := json.Marshal(v)
return a
}
}

View File

@@ -1,6 +1,8 @@
FOLDER=$(GOOS).$(GOARCH)
SUFFIX=$(GOOS)_$(GOARCH)
COMMIT_HASH=$(shell git rev-parse HEAD)
GIT_BRANCH=$(shell git branch --show-current)
GIT_BRANCH=$(shell git branch --show-current | tr '[:upper:]' '[:lower:]')
GIT_VERSION=$(shell git branch --show-current | tr '[:upper:]' '[:lower:]')
BUILD_TIMESTAMP=$(shell date +%s)
.PHONY: help
.DEFAULT_GOAL := help
@@ -12,10 +14,16 @@ install:
go install mizu.go
build: ## build mizu CLI binary (select platform via GOOS / GOARCH env variables)
go build -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' -X 'github.com/up9inc/mizu/cli/mizu.Branch=$(GIT_BRANCH)'" -o bin/$(FOLDER)/mizu mizu.go
go build -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' \
-X 'github.com/up9inc/mizu/cli/mizu.Branch=$(GIT_BRANCH)' \
-X 'github.com/up9inc/mizu/cli/mizu.BuildTimestamp=$(BUILD_TIMESTAMP)' \
-X 'github.com/up9inc/mizu/cli/mizu.SemVer=$(SEM_VER)'" \
-o bin/mizu_$(SUFFIX) mizu.go
(cd bin && shasum -a 256 mizu_${SUFFIX} > mizu_${SUFFIX}.sha256)
build-all: ## build for all supported platforms
@echo "Compiling for every OS and Platform"
@mkdir -p bin && echo "SHA256 checksums available for compiled binaries \n\nRun \`shasum -a 256 -c mizu_OS_ARCH.sha256\` to verify\n\n" > bin/checksums.md
@$(MAKE) build GOOS=darwin GOARCH=amd64
@$(MAKE) build GOOS=linux GOARCH=amd64
@# $(MAKE) GOOS=windows GOARCH=amd64

View File

@@ -10,11 +10,13 @@ import (
)
type MizuTapOptions struct {
GuiPort uint16
Namespace string
KubeConfigPath string
MizuImage string
MizuPodPort uint16
GuiPort uint16
Namespace string
AllNamespaces bool
KubeConfigPath string
MizuImage string
MizuPodPort uint16
PlainTextFilterRegexes []string
}
@@ -47,7 +49,9 @@ func init() {
tapCmd.Flags().Uint16VarP(&mizuTapOptions.GuiPort, "gui-port", "p", 8899, "Provide a custom port for the web interface webserver")
tapCmd.Flags().StringVarP(&mizuTapOptions.Namespace, "namespace", "n", "", "Namespace selector")
tapCmd.Flags().BoolVarP(&mizuTapOptions.AllNamespaces, "all-namespaces", "A", false, "Tap all namespaces")
tapCmd.Flags().StringVarP(&mizuTapOptions.KubeConfigPath, "kube-config", "k", "", "Path to kube-config file")
tapCmd.Flags().StringVarP(&mizuTapOptions.MizuImage, "mizu-image", "", fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:latest", mizu.Branch), "Custom image for mizu collector")
tapCmd.Flags().Uint16VarP(&mizuTapOptions.MizuPodPort, "mizu-port", "", 8899, "Port which mizu cli will attempt to forward from the mizu collector pod")
tapCmd.Flags().StringArrayVarP(&mizuTapOptions.PlainTextFilterRegexes, "regex-masking", "r", nil, "List of regex expressions that are used to filter matching values from text/plain http bodies")
}

View File

@@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"github.com/up9inc/mizu/shared"
"os"
"os/signal"
"regexp"
@@ -26,7 +27,12 @@ const (
var currentlyTappedPods []core.Pod
func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
kubernetesProvider := kubernetes.NewProvider(tappingOptions.KubeConfigPath, tappingOptions.Namespace)
mizuApiFilteringOptions, err := getMizuApiFilteringOptions(tappingOptions)
if err != nil {
return
}
kubernetesProvider := kubernetes.NewProvider(tappingOptions.KubeConfigPath)
defer cleanUpMizuResources(kubernetesProvider)
ctx, cancel := context.WithCancel(context.Background())
@@ -38,12 +44,12 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
currentlyTappedPods = matchingPods
}
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(ctx, kubernetesProvider, currentlyTappedPods)
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(currentlyTappedPods)
if err != nil {
return
}
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions); err != nil {
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, tappingOptions, mizuApiFilteringOptions); err != nil {
return
}
@@ -57,8 +63,8 @@ func RunMizuTap(podRegexQuery *regexp.Regexp, tappingOptions *MizuTapOptions) {
// TODO handle incoming traffic from tapper using a channel
}
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions) error {
if err := createMizuAggregator(ctx, kubernetesProvider, tappingOptions); err != nil {
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
if err := createMizuAggregator(ctx, kubernetesProvider, tappingOptions, mizuApiFilteringOptions); err != nil {
return err
}
@@ -69,11 +75,11 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro
return nil
}
func createMizuAggregator(ctx context.Context, kubernetesProvider *kubernetes.Provider, tappingOptions *MizuTapOptions) error {
func createMizuAggregator(ctx context.Context, kubernetesProvider *kubernetes.Provider, tappingOptions *MizuTapOptions, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
var err error
mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider)
_, err = kubernetesProvider.CreateMizuAggregatorPod(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName, tappingOptions.MizuImage, mizuServiceAccountExists)
_, err = kubernetesProvider.CreateMizuAggregatorPod(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName, tappingOptions.MizuImage, mizuServiceAccountExists, mizuApiFilteringOptions)
if err != nil {
fmt.Printf("Error creating mizu collector pod: %v\n", err)
return err
@@ -88,6 +94,24 @@ func createMizuAggregator(ctx context.Context, kubernetesProvider *kubernetes.Pr
return nil
}
func getMizuApiFilteringOptions(tappingOptions *MizuTapOptions) (*shared.TrafficFilteringOptions, error) {
if tappingOptions.PlainTextFilterRegexes == nil || len(tappingOptions.PlainTextFilterRegexes) == 0 {
return nil, nil
}
compiledRegexSlice := make([]*shared.SerializableRegexp, 0)
for _, regexStr := range tappingOptions.PlainTextFilterRegexes {
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
if err != nil {
fmt.Printf("Regex %s is invalid: %v", regexStr, err)
return nil, err
}
compiledRegexSlice = append(compiledRegexSlice, compiledRegex)
}
return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice}, nil
}
func createMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, tappingOptions *MizuTapOptions) error {
if err := kubernetesProvider.ApplyMizuTapperDaemonSet(
ctx,
@@ -109,20 +133,20 @@ func createMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) {
fmt.Printf("\nRemoving mizu resources\n")
removalCtx, _ := context.WithTimeout(context.Background(), 5 * time.Second)
removalCtx, _ := context.WithTimeout(context.Background(), 5*time.Second)
if err := kubernetesProvider.RemovePod(removalCtx, mizu.ResourcesNamespace, mizu.AggregatorPodName); err != nil {
fmt.Printf("Error removing Pod %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err);
fmt.Printf("Error removing Pod %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err)
}
if err := kubernetesProvider.RemoveService(removalCtx, mizu.ResourcesNamespace, mizu.AggregatorPodName); err != nil {
fmt.Printf("Error removing Service %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err);
fmt.Printf("Error removing Service %s in namespace %s: %s (%v,%+v)\n", mizu.AggregatorPodName, mizu.ResourcesNamespace, err, err, err)
}
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, mizu.ResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
fmt.Printf("Error removing DaemonSet %s in namespace %s: %s (%v,%+v)\n", mizu.TapperDaemonSetName, mizu.ResourcesNamespace, err, err, err);
fmt.Printf("Error removing DaemonSet %s in namespace %s: %s (%v,%+v)\n", mizu.TapperDaemonSetName, mizu.ResourcesNamespace, err, err, err)
}
}
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, podRegex *regexp.Regexp, tappingOptions *MizuTapOptions) {
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, kubernetesProvider.Namespace), podRegex)
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, getNamespace(tappingOptions, kubernetesProvider)), podRegex)
restartTappers := func() {
if matchingPods, err := kubernetesProvider.GetAllPodsMatchingRegex(ctx, podRegex); err != nil {
@@ -132,7 +156,7 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
currentlyTappedPods = matchingPods
}
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(ctx, kubernetesProvider, currentlyTappedPods)
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(currentlyTappedPods)
if err != nil {
fmt.Printf("Error building node to ips map: %s (%v,%+v)\n", err, err, err)
cancel()
@@ -147,14 +171,14 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
for {
select {
case newTarget := <- added:
case newTarget := <-added:
fmt.Printf("+%s\n", newTarget.Name)
case removedTarget := <- removed:
case removedTarget := <-removed:
fmt.Printf("-%s\n", removedTarget.Name)
restartTappersDebouncer.SetOn()
case modifiedTarget := <- modified:
case modifiedTarget := <-modified:
// Act only if the modified pod has already obtained an IP address.
// After filtering for IPs, on a normal pod restart this includes the following events:
// - Pod deletion
@@ -165,11 +189,11 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
restartTappersDebouncer.SetOn()
}
case <- errorChan:
case <-errorChan:
// TODO: Does this also perform cleanup?
cancel()
case <- ctx.Done():
case <-ctx.Done():
return
}
}
@@ -182,13 +206,13 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
var portForward *kubernetes.PortForward
for {
select {
case <- added:
case <-added:
continue
case <- removed:
case <-removed:
fmt.Printf("%s removed\n", mizu.AggregatorPodName)
cancel()
return
case modifiedPod := <- modified:
case modifiedPod := <-modified:
if modifiedPod.Status.Phase == "Running" && !isPodReady {
isPodReady = true
var err error
@@ -200,16 +224,16 @@ func portForwardApiPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
}
}
case <- time.After(25 * time.Second):
case <-time.After(25 * time.Second):
if !isPodReady {
fmt.Printf("error: %s pod was not ready in time", mizu.AggregatorPodName)
cancel()
}
case <- errorChan:
case <-errorChan:
cancel()
case <- ctx.Done():
case <-ctx.Done():
if portForward != nil {
portForward.Stop()
}
@@ -225,11 +249,7 @@ func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.P
return false
}
if !mizuRBACExists {
var versionString = mizu.Version
if mizu.GitCommitHash != "" {
versionString += "-" + mizu.GitCommitHash
}
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.ResourcesNamespace, versionString)
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.ResourcesNamespace, mizu.RBACVersion)
if err != nil {
fmt.Printf("warning: could not create mizu rbac resources %v\n", err)
return false
@@ -238,12 +258,12 @@ func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.P
return true
}
func getNodeHostToTappedPodIpsMap(ctx context.Context, kubernetesProvider *kubernetes.Provider, tappedPods []core.Pod) (map[string][]string, error) {
func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) (map[string][]string, error) {
nodeToTappedPodIPMap := make(map[string][]string, 0)
for _, pod := range tappedPods {
existingList := nodeToTappedPodIPMap[pod.Spec.NodeName]
if existingList == nil {
nodeToTappedPodIPMap[pod.Spec.NodeName] = []string {pod.Status.PodIP}
nodeToTappedPodIPMap[pod.Spec.NodeName] = []string{pod.Status.PodIP}
} else {
nodeToTappedPodIPMap[pod.Spec.NodeName] = append(nodeToTappedPodIPMap[pod.Spec.NodeName], pod.Status.PodIP)
}
@@ -257,9 +277,9 @@ func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
// block until ctx cancel is called or termination signal is received
select {
case <- ctx.Done():
case <-ctx.Done():
break
case <- sigChan:
case <-sigChan:
cancel()
}
}
@@ -273,7 +293,7 @@ func syncApiStatus(ctx context.Context, cancel context.CancelFunc, tappingOption
for {
select {
case <- ctx.Done():
case <-ctx.Done():
return
default:
err = controlSocket.SendNewTappedPodsListMessage(currentlyTappedPods)
@@ -285,3 +305,13 @@ func syncApiStatus(ctx context.Context, cancel context.CancelFunc, tappingOption
}
}
func getNamespace(tappingOptions *MizuTapOptions, kubernetesProvider *kubernetes.Provider) string {
if tappingOptions.AllNamespaces {
return mizu.K8sAllNamespaces
} else if len(tappingOptions.Namespace) > 0 {
return tappingOptions.Namespace
} else {
return kubernetesProvider.CurrentNamespace()
}
}

View File

@@ -3,19 +3,38 @@ package cmd
import (
"fmt"
"github.com/up9inc/mizu/cli/mizu"
"strconv"
"time"
"github.com/spf13/cobra"
)
type MizuVersionOptions struct {
DebugInfo bool
}
var mizuVersionOptions = &MizuVersionOptions{}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version info",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("%s (%s) %s\n", mizu.Version, mizu.Branch, mizu.GitCommitHash)
if mizuVersionOptions.DebugInfo {
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
fmt.Printf("Version: %s \nBranch: %s (%s) \n", mizu.SemVer, mizu.Branch, mizu.GitCommitHash)
fmt.Printf("Build Time: %s (%s)\n", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
} else {
fmt.Printf("Version: %s (%s)\n", mizu.SemVer, mizu.Branch)
}
return nil
},
}
func init() {
rootCmd.AddCommand(versionCmd)
versionCmd.Flags().BoolVarP(&mizuVersionOptions.DebugInfo, "debug", "d", false, "Provide all information about version")
}

View File

@@ -1,7 +1,6 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
@@ -9,7 +8,7 @@ var viewCmd = &cobra.Command{
Use: "view",
Short: "Open GUI in browser",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Not implemented")
runMizuView()
return nil
},
}

33
cli/cmd/viewRunner.go Normal file
View File

@@ -0,0 +1,33 @@
package cmd
import (
"context"
"fmt"
"github.com/up9inc/mizu/cli/kubernetes"
"github.com/up9inc/mizu/cli/mizu"
"net/http"
)
func runMizuView() {
kubernetesProvider := kubernetes.NewProvider("")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
exists, err := kubernetesProvider.DoesServicesExist(ctx, mizu.ResourcesNamespace, mizu.AggregatorPodName)
if err != nil {
panic(err)
}
if !exists {
fmt.Printf("The %s service not found\n", mizu.AggregatorPodName)
return
}
_, err = http.Get("http://localhost:8899/")
if err == nil {
fmt.Printf("Found a running service %s and open port 8899\n", mizu.AggregatorPodName)
return
}
fmt.Printf("Found service %s, creating port forwarding to 8899\n", mizu.AggregatorPodName)
portForwardApiPod(ctx, kubernetesProvider, cancel, &MizuTapOptions{GuiPort: 8899, MizuPodPort: 8899})
}

View File

@@ -6,19 +6,20 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/up9inc/mizu/cli/mizu"
"path/filepath"
"regexp"
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
applyconfmeta "k8s.io/client-go/applyconfigurations/meta/v1"
applyconfcore "k8s.io/client-go/applyconfigurations/core/v1"
"github.com/up9inc/mizu/shared"
core "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/watch"
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
applyconfcore "k8s.io/client-go/applyconfigurations/core/v1"
applyconfmeta "k8s.io/client-go/applyconfigurations/meta/v1"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
@@ -42,7 +43,7 @@ const (
fieldManagerName = "mizu-manager"
)
func NewProvider(kubeConfigPath string, overrideNamespace string) *Provider {
func NewProvider(kubeConfigPath string) *Provider {
kubernetesConfig := loadKubernetesConfiguration(kubeConfigPath)
restClientConfig, err := kubernetesConfig.ClientConfig()
if err != nil {
@@ -50,25 +51,18 @@ func NewProvider(kubeConfigPath string, overrideNamespace string) *Provider {
}
clientSet := getClientSet(restClientConfig)
var namespace string
if len(overrideNamespace) > 0 {
namespace = overrideNamespace
} else {
configuredNamespace, _, err := kubernetesConfig.Namespace()
if err != nil {
panic(err)
}
namespace = configuredNamespace
}
return &Provider{
clientSet: clientSet,
kubernetesConfig: kubernetesConfig,
clientConfig: *restClientConfig,
Namespace: namespace,
}
}
func (provider *Provider) CurrentNamespace() string {
ns, _, _ := provider.kubernetesConfig.Namespace()
return ns
}
func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) watch.Interface {
watcher, err := provider.clientSet.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{Watch: true})
if err != nil {
@@ -77,20 +71,16 @@ func (provider *Provider) GetPodWatcher(ctx context.Context, namespace string) w
return watcher
}
func (provider *Provider) GetPods(ctx context.Context, namespace string) {
pods, err := provider.clientSet.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace string, podName string, podImage string, linkServiceAccount bool, mizuApiFilteringOptions *shared.TrafficFilteringOptions) (*core.Pod, error) {
marshaledFilteringOptions, err := json.Marshal(mizuApiFilteringOptions)
if err != nil {
panic(err.Error())
return nil, err
}
fmt.Printf("There are %d pods in Namespace %s\n", len(pods.Items), namespace)
}
func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace string, podName string, podImage string, linkServiceAccount bool) (*core.Pod, error) {
pod := &core.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: map[string]string{"app": podName},
Labels: map[string]string{"app": podName},
},
Spec: core.PodSpec{
Containers: []core.Container{
@@ -98,16 +88,20 @@ func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace
Name: podName,
Image: podImage,
ImagePullPolicy: core.PullAlways,
Command: []string {"./mizuagent", "--aggregator"},
Command: []string{"./mizuagent", "--aggregator"},
Env: []core.EnvVar{
{
Name: "HOST_MODE",
Name: shared.HostModeEnvVar,
Value: "1",
},
{
Name: shared.MizuFilteringOptionsEnvVar,
Value: string(marshaledFilteringOptions),
},
},
},
},
DNSPolicy: "ClusterFirstWithHostNet",
DNSPolicy: core.DNSClusterFirstWithHostNet,
TerminationGracePeriodSeconds: new(int64),
// Affinity: TODO: define node selector for all relevant nodes for this mizu instance
},
@@ -122,19 +116,19 @@ func (provider *Provider) CreateMizuAggregatorPod(ctx context.Context, namespace
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string) (*core.Service, error) {
service := core.Service{
ObjectMeta: metav1.ObjectMeta{
Name: serviceName,
Name: serviceName,
Namespace: namespace,
},
Spec: core.ServiceSpec{
Ports: []core.ServicePort {{TargetPort: intstr.FromInt(8899), Port: 80}},
Type: core.ServiceTypeClusterIP,
Ports: []core.ServicePort{{TargetPort: intstr.FromInt(8899), Port: 80}},
Type: core.ServiceTypeClusterIP,
Selector: map[string]string{"app": appLabelValue},
},
}
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
}
func (provider *Provider) DoesMizuRBACExist(ctx context.Context, namespace string) (bool, error){
func (provider *Provider) DoesMizuRBACExist(ctx context.Context, namespace string) (bool, error) {
serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{})
var statusError *k8serrors.StatusError
@@ -150,7 +144,22 @@ func (provider *Provider) DoesMizuRBACExist(ctx context.Context, namespace strin
return serviceAccount != nil, nil
}
func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string ,version string) error {
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, serviceName string) (bool, error) {
service, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
var statusError *k8serrors.StatusError
if errors.As(err, &statusError) {
if statusError.ErrStatus.Reason == metav1.StatusReasonNotFound {
return false, nil
}
}
if err != nil {
return false, err
}
return service != nil, nil
}
func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, version string) error {
clusterRoleName := "mizu-cluster-role"
serviceAccount := &core.ServiceAccount{
@@ -162,25 +171,25 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string ,
}
clusterRole := &rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: clusterRoleName,
Name: clusterRoleName,
Labels: map[string]string{"mizu-cli-version": version},
},
Rules: []rbac.PolicyRule{
{
APIGroups: []string {"", "extensions", "apps"},
Resources: []string {"pods", "services", "endpoints"},
Verbs: []string {"list", "get", "watch"},
APIGroups: []string{"", "extensions", "apps"},
Resources: []string{"pods", "services", "endpoints"},
Verbs: []string{"list", "get", "watch"},
},
},
}
clusterRoleBinding := &rbac.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "mizu-cluster-role-binding",
Name: "mizu-cluster-role-binding",
Labels: map[string]string{"mizu-cli-version": version},
},
RoleRef: rbac.RoleRef{
Name: clusterRoleName,
Kind: "ClusterRole",
Name: clusterRoleName,
Kind: "ClusterRole",
APIGroup: "rbac.authorization.k8s.io",
},
Subjects: []rbac.Subject{
@@ -232,24 +241,51 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
agentContainer.WithSecurityContext(applyconfcore.SecurityContext().WithPrivileged(privileged))
agentContainer.WithCommand("./mizuagent", "-i", "any", "--tap", "--hardump", "--aggregator-address", fmt.Sprintf("ws://%s/wsTapper", aggregatorPodIp))
agentContainer.WithEnv(
applyconfcore.EnvVar().WithName("HOST_MODE").WithValue("1"),
applyconfcore.EnvVar().WithName("AGGREGATOR_ADDRESS").WithValue(aggregatorPodIp),
applyconfcore.EnvVar().WithName("TAPPED_ADDRESSES_PER_HOST").WithValue(string(nodeToTappedPodIPMapJsonStr)),
applyconfcore.EnvVar().WithName(shared.HostModeEnvVar).WithValue("1"),
applyconfcore.EnvVar().WithName(shared.TappedAddressesPerNodeDictEnvVar).WithValue(string(nodeToTappedPodIPMapJsonStr)),
)
agentContainer.WithEnv(
applyconfcore.EnvVar().WithName("NODE_NAME").WithValueFrom(
applyconfcore.EnvVar().WithName(shared.NodeNameEnvVar).WithValueFrom(
applyconfcore.EnvVarSource().WithFieldRef(
applyconfcore.ObjectFieldSelector().WithAPIVersion("v1").WithFieldPath("spec.nodeName"),
),
),
)
podSpec := applyconfcore.PodSpec().WithHostNetwork(true).WithDNSPolicy("ClusterFirstWithHostNet").WithTerminationGracePeriodSeconds(0)
nodeNames := make([]string, 0, len(nodeToTappedPodIPMap))
for nodeName := range nodeToTappedPodIPMap {
nodeNames = append(nodeNames, nodeName)
}
nodeSelectorRequirement := applyconfcore.NodeSelectorRequirement()
nodeSelectorRequirement.WithKey("kubernetes.io/hostname")
nodeSelectorRequirement.WithOperator(core.NodeSelectorOpIn)
nodeSelectorRequirement.WithValues(nodeNames...)
nodeSelectorTerm := applyconfcore.NodeSelectorTerm()
nodeSelectorTerm.WithMatchExpressions(nodeSelectorRequirement)
nodeSelector := applyconfcore.NodeSelector()
nodeSelector.WithNodeSelectorTerms(nodeSelectorTerm)
nodeAffinity := applyconfcore.NodeAffinity()
nodeAffinity.WithRequiredDuringSchedulingIgnoredDuringExecution(nodeSelector)
affinity := applyconfcore.Affinity()
affinity.WithNodeAffinity(nodeAffinity)
noExecuteToleration := applyconfcore.Toleration()
noExecuteToleration.WithOperator(core.TolerationOpExists)
noExecuteToleration.WithEffect(core.TaintEffectNoExecute)
noScheduleToleration := applyconfcore.Toleration()
noScheduleToleration.WithOperator(core.TolerationOpExists)
noScheduleToleration.WithEffect(core.TaintEffectNoSchedule)
podSpec := applyconfcore.PodSpec()
podSpec.WithHostNetwork(true)
podSpec.WithDNSPolicy(core.DNSClusterFirstWithHostNet)
podSpec.WithTerminationGracePeriodSeconds(0)
if linkServiceAccount {
podSpec.WithServiceAccountName(serviceAccountName)
}
podSpec.WithContainers(agentContainer)
podSpec.WithAffinity(affinity)
podSpec.WithTolerations(noExecuteToleration, noScheduleToleration)
podTemplate := applyconfcore.PodTemplateSpec()
podTemplate.WithLabels(map[string]string{"app": tapperPodName})
@@ -266,7 +302,7 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
}
func (provider *Provider) GetAllPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp) ([]core.Pod, error) {
pods, err := provider.clientSet.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
pods, err := provider.clientSet.CoreV1().Pods(mizu.K8sAllNamespaces).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}

View File

@@ -1,9 +1,11 @@
package mizu
var (
Version = "v0.0.1"
Branch = "develop"
GitCommitHash = "" // this var is overridden using ldflags in makefile when building
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"
)
const (
@@ -11,4 +13,5 @@ const (
TapperDaemonSetName = "mizu-tapper-daemon-set"
AggregatorPodName = "mizu-collector"
TapperPodName = "mizu-tapper"
K8sAllNamespaces = ""
)

8
shared/consts.go Normal file
View File

@@ -0,0 +1,8 @@
package shared
const (
MizuFilteringOptionsEnvVar = "SENSITIVE_DATA_FILTERING_OPTIONS"
HostModeEnvVar = "HOST_MODE"
NodeNameEnvVar = "NODE_NAME"
TappedAddressesPerNodeDictEnvVar = "TAPPED_ADDRESSES_PER_HOST"
)

View File

@@ -33,3 +33,7 @@ func CreateWebSocketStatusMessage(tappingStatus TapStatus) WebSocketStatusMessag
TappingStatus: tappingStatus,
}
}
type TrafficFilteringOptions struct {
PlainTextMaskingRegexes []*SerializableRegexp
}

View File

@@ -0,0 +1,30 @@
package shared
import "regexp"
type SerializableRegexp struct {
regexp.Regexp
}
func CompileRegexToSerializableRegexp(expr string) (*SerializableRegexp, error) {
re, err := regexp.Compile(expr)
if err != nil {
return nil, err
}
return &SerializableRegexp{*re}, nil
}
// UnmarshalText is by json.Unmarshal.
func (r *SerializableRegexp) UnmarshalText(text []byte) error {
rr, err := CompileRegexToSerializableRegexp(string(text))
if err != nil {
return err
}
*r = *rr
return nil
}
// MarshalText is used by json.Marshal.
func (r *SerializableRegexp) MarshalText() ([]byte, error) {
return []byte(r.String()), nil
}

View File

@@ -1,5 +1,5 @@
import './style/StatusBar.sass';
import React from "react";
import React, {useState} from "react";
export interface TappingStatusPod {
name: string;
@@ -15,14 +15,31 @@ export interface Props {
}
const pluralize = (noun: string, amount: number) => {
return `${noun}${amount != 1 ? 's' : ''}`
return `${noun}${amount !== 1 ? 's' : ''}`
}
export const StatusBar: React.FC<Props> = ({tappingStatus}) => {
const [expandedBar, setExpandedBar] = useState(false);
const uniqueNamespaces = Array.from(new Set(tappingStatus.pods.map(pod => pod.namespace)));
const amountOfPods = tappingStatus.pods.length;
return <div className='StatusBar'>
<span>{`Tapping ${amountOfPods} ${pluralize('pod', amountOfPods)} in ${pluralize('namespace', uniqueNamespaces.length)} ${uniqueNamespaces.join(", ")}`}</span>
return <div className={'statusBar' + (expandedBar ? ' expandedStatusBar' : "")} onMouseOver={() => setExpandedBar(true)} onMouseLeave={() => setExpandedBar(false)}>
<div className="podsCount">{`Tapping ${amountOfPods} ${pluralize('pod', amountOfPods)} in ${pluralize('namespace', uniqueNamespaces.length)} ${uniqueNamespaces.join(", ")}`}</div>
{expandedBar && <div style={{marginTop: 20}}>
<table>
<tr>
<th>Pod name</th>
<th>Namespace</th>
</tr>
<tbody>
{tappingStatus.pods.map(pod => <tr>
<td>{pod.name}</td>
<td>{pod.namespace}</td>
</tr>)}
</tbody>
</table>
</div>}
</div>;
}

View File

@@ -1,20 +1,35 @@
@import 'variables.module.scss'
.StatusBar
.statusBar
position: absolute
transform: translate(-50%, -3px)
left: 50%
z-index: 9999
min-width: 200px
height: 32px
background: $blue-color
color: $light-blue-color
color: rgba(255,255,255,0.75)
border-bottom-left-radius: 8px
border-bottom-right-radius: 8px
top: 0
display: flex
align-items: center
padding: 2px 10px
user-select: none
font-size: 14px
opacity: 0.8
transition: max-height 2s ease-out
width: auto
max-height: 32px
overflow: hidden
.podsCount
display: flex
justify-content: center
padding: 8px
font-weight: 600
th
text-align: left
td
padding-right: 15px
padding-top: 5px
.expandedStatusBar
max-height: 100vh
padding-bottom: 15px