Compare commits

..

15 Commits

Author SHA1 Message Date
M. Mert Yıldıran
352567c56e Fix the typo in protocolAbbreviation field of MizuEntry (#307) 2021-09-28 16:45:04 +03:00
lirazyehezkel
51fc3307be Mizu rules font (#306) 2021-09-27 14:18:10 +03:00
M. Mert Yıldıran
cdf1c39a52 Omit the RULES tab if the policy rules feature is inactive (#303)
* Omit the `RULES` tab if the policy rules feature is inactive (WIP)

* Propagate the boolean value `isRulesEnabled` from file read error to UI

* Remove the debug log
2021-09-25 18:15:54 +03:00
M. Mert Yıldıran
db1f7d34cf Omit the RESPONSE tab and elapsedTime if the response is empty (#298)
* Omit the `RESPONSE` tab and `elapsedTime` if the `response` is empty

* Use the `hidden` attribute instead
2021-09-24 13:49:20 +03:00
Nimrod Gilboa Markevich
9212c195b4 Improve cloud resources cleanup (#215) 2021-09-23 20:51:37 +03:00
M. Mert Yıldıran
7b333556d0 Add Redis Serialization Protocol support (#290)
* Bring in the files

* Add request-response pair matcher for Redis

* Implement the `Represent` method

* Update `representGeneric` method signature

* Don't export `IntToByteArr`

* Remove unused `newRedisInputStream` method

* Return the errors as string

* Adapt to the latest change in the `develop`
2021-09-23 17:09:00 +03:00
M. Mert Yıldıran
8ba96acf05 Don't omit the Latency field in BaseEntryDetails (#302) 2021-09-23 14:58:20 +03:00
RoyUP9
f164e54fee changed test rules config to readonly (#301) 2021-09-23 09:13:17 +03:00
M. Mert Yıldıran
649b733ba1 Don't redact :authority pseudo-header field (#300) 2021-09-23 09:12:04 +03:00
M. Mert Yıldıran
e8ea93cb64 Upgrade react-scrollable-feed-virtualized version from 1.4.2 to 1.4.3 (#299) 2021-09-23 08:36:44 +03:00
RoyUP9
5dacd41ba9 renamed traffic-validation to traffic-validation-file (#296) 2021-09-22 11:21:43 +03:00
Igor Gov
7f837fe947 Improving logs and cleaning un-referenced code (#295) 2021-09-22 11:14:20 +03:00
RoyUP9
02bd7883cb fixed general stats (#293) 2021-09-22 10:52:53 +03:00
RoyUP9
1841798646 Fix: analysis feature (#292) 2021-09-22 09:44:02 +03:00
Igor Gov
749bee6d55 Using rlog in agent and tapper & removing unreferenced code (#289)
* .
2021-09-22 06:24:19 +03:00
39 changed files with 1380 additions and 361 deletions

View File

@@ -152,12 +152,12 @@ Web interface is now available at http://localhost:8899
```
Any request that contains `User-Agent` header with one of the specified values (`kube-probe` or `prometheus`) will not be captured
### API Rules validation
### Traffic validation rules
This feature allows you to define set of simple rules, and test the API against them.
This feature allows you to define set of simple rules, and test the traffic against them.
Such validation may test response for specific JSON fields, headers, etc.
Please see [API RULES](docs/POLICY_RULES.md) page for more details and syntax.
Please see [TRAFFIC RULES](docs/POLICY_RULES.md) page for more details and syntax.
## How to Run local UI

View File

@@ -61,10 +61,10 @@ func main() {
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions)
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
// go api.StartReadingOutbound(outboundLinkOutputChannel)
hostApi(nil)
} else if *tapperMode {
rlog.Infof("Starting tapper, websocket address: %s", *apiServerAddress)
if *apiServerAddress == "" {
panic("API server address must be provided with --api-server-address when using --tap")
}
@@ -77,13 +77,13 @@ func main() {
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions, filteringOptions)
socketConnection, err := shared.ConnectToSocketServer(*apiServerAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
socketConnection, err := utils.ConnectToSocketServer(*apiServerAddress)
if err != nil {
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
}
rlog.Infof("Connected successfully to websocket %s", *apiServerAddress)
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
// go pipeOutboundLinksChannelToSocket(socketConnection, outboundLinkOutputChannel)
} else if *apiServerMode {
api.StartResolving(*namespace)
@@ -122,7 +122,7 @@ func loadExtensions() {
extensionsMap = make(map[string]*tapApi.Extension)
for i, file := range files {
filename := file.Name()
log.Printf("Loading extension: %s\n", filename)
rlog.Infof("Loading extension: %s\n", filename)
extension := &tapApi.Extension{
Path: path.Join(extensionsDir, filename),
}
@@ -290,7 +290,7 @@ func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-cha
for messageData := range messageDataChannel {
marshaledData, err := models.CreateWebsocketTappedEntryMessage(messageData)
if err != nil {
rlog.Infof("error converting message to json %s, (%v,%+v)\n", err, err, err)
rlog.Errorf("error converting message to json %v, err: %s, (%v,%+v)", messageData, err, err, err)
continue
}
@@ -298,26 +298,8 @@ func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-cha
// and goes into the intermediate WebSocket.
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
if err != nil {
rlog.Infof("error sending message through socket server %s, (%v,%+v)\n", err, err, err)
rlog.Errorf("error sending message through socket server %v, err: %s, (%v,%+v)", messageData, err, err, err)
continue
}
}
}
func pipeOutboundLinksChannelToSocket(connection *websocket.Conn, outboundLinkChannel <-chan *tap.OutboundLink) {
for outboundLink := range outboundLinkChannel {
if outboundLink.SuggestedProtocol == tap.TLSProtocol {
marshaledData, err := models.CreateWebsocketOutboundLinkMessage(outboundLink)
if err != nil {
rlog.Infof("Error converting outbound link to json %s, (%v,%+v)", err, err, err)
continue
}
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
if err != nil {
rlog.Infof("error sending outbound link message through socket server %s, (%v,%+v)", err, err, err)
continue
}
}
}
}

View File

@@ -7,7 +7,7 @@ import (
"fmt"
"mizuserver/pkg/database"
"mizuserver/pkg/holder"
"net/url"
"mizuserver/pkg/providers"
"os"
"path"
"sort"
@@ -18,7 +18,6 @@ import (
"github.com/google/martian/har"
"github.com/romana/rlog"
"github.com/up9inc/mizu/tap"
tapApi "github.com/up9inc/mizu/tap/api"
"mizuserver/pkg/models"
@@ -59,8 +58,10 @@ func StartReadingEntries(harChannel <-chan *tapApi.OutputChannelItem, workingDir
}
func startReadingFiles(workingDir string) {
err := os.MkdirAll(workingDir, os.ModePerm)
utils.CheckErr(err)
if err := os.MkdirAll(workingDir, os.ModePerm); err != nil {
rlog.Errorf("Failed to make dir: %s, err: %v", workingDir, err)
return
}
for true {
dir, _ := os.Open(workingDir)
@@ -88,17 +89,6 @@ func startReadingFiles(workingDir string) {
decErr := json.NewDecoder(bufio.NewReader(file)).Decode(&inputHar)
utils.CheckErr(decErr)
// for _, entry := range inputHar.Log.Entries {
// time.Sleep(time.Millisecond * 250)
// // connectionInfo := &tap.ConnectionInfo{
// // ClientIP: fileInfo.Name(),
// // ClientPort: "",
// // ServerIP: "",
// // ServerPort: "",
// // IsOutgoing: false,
// // }
// // saveHarToDb(entry, connectionInfo)
// }
rmErr := os.Remove(inputFilePath)
utils.CheckErr(rmErr)
}
@@ -110,6 +100,8 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
}
for item := range outputItems {
providers.EntryAdded()
extension := extensionsMap[item.Protocol.Name]
resolvedSource, resolvedDestionation := resolveIP(item.ConnectionInfo)
mizuEntry := extension.Dissector.Analyze(item, primitive.NewObjectID().Hex(), resolvedSource, resolvedDestionation)
@@ -121,9 +113,8 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
json.Unmarshal([]byte(mizuEntry.Entry), &pair)
harEntry, err := utils.NewEntry(&pair)
if err == nil {
rules, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
baseEntry.Rules = rules
baseEntry.Latency = mizuEntry.ElapsedTime
}
}
@@ -132,13 +123,6 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
}
}
func StartReadingOutbound(outboundLinkChannel <-chan *tap.OutboundLink) {
// tcpStreamFactory will block on write to channel. Empty channel to unblock.
// TODO: Make write to channel optional.
for range outboundLinkChannel {
}
}
func resolveIP(connectionInfo *tapApi.ConnectionInfo) (resolvedSource string, resolvedDestination string) {
if k8sResolver != nil {
unresolvedSource := connectionInfo.ClientIP
@@ -161,12 +145,6 @@ func resolveIP(connectionInfo *tapApi.ConnectionInfo) (resolvedSource string, re
return resolvedSource, resolvedDestination
}
func getServiceNameFromUrl(inputUrl string) (string, string) {
parsed, err := url.Parse(inputUrl)
utils.CheckErr(err)
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host), parsed.Path
}
func CheckIsServiceIP(address string) bool {
if k8sResolver == nil {
return false

View File

@@ -143,11 +143,13 @@ func GetEntry(c *gin.Context) {
protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData)
var rules []map[string]interface{}
var isRulesEnabled bool
if entryData.ProtocolName == "http" {
var pair tapApi.RequestResponsePair
json.Unmarshal([]byte(entryData.Entry), &pair)
harEntry, _ := utils.NewEntry(&pair)
_, rulesMatched := models.RunValidationRulesState(*harEntry, entryData.Service)
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entryData.Service)
isRulesEnabled = _isRulesEnabled
inrec, _ := json.Marshal(rulesMatched)
json.Unmarshal(inrec, &rules)
}
@@ -158,6 +160,7 @@ func GetEntry(c *gin.Context) {
BodySize: bodySize,
Data: entryData,
Rules: rules,
IsRulesEnabled: isRulesEnabled,
})
}

View File

@@ -61,7 +61,7 @@ func GetEntriesFromDb(timestampFrom int64, timestampTo int64, protocolName *stri
order := OrderDesc
protocolNameCondition := "1 = 1"
if protocolName != nil {
protocolNameCondition = fmt.Sprintf("protocolKey = '%s'", *protocolName)
protocolNameCondition = fmt.Sprintf("protocolName = '%s'", *protocolName)
}
var entries []tapApi.MizuEntry

View File

@@ -97,8 +97,8 @@ type ExtendedCreator struct {
Source *string `json:"_source"`
}
func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched) {
resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched, bool) {
resultPolicyToSend, isEnabled := rules.MatchRequestPolicy(harEntry, service)
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend, isEnabled
}

View File

@@ -8,6 +8,8 @@ import (
"regexp"
"strings"
"github.com/romana/rlog"
"github.com/google/martian/har"
"github.com/up9inc/mizu/shared"
jsonpath "github.com/yalp/jsonpath"
@@ -42,9 +44,11 @@ func ValidateService(serviceFromRule string, service string) bool {
return true
}
func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched {
enforcePolicy, _ := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
var resultPolicyToSend []RulesMatched
func MatchRequestPolicy(harEntry har.Entry, service string) (resultPolicyToSend []RulesMatched, isEnabled bool) {
enforcePolicy, err := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
if err == nil {
isEnabled = true
}
for _, rule := range enforcePolicy.Rules {
if !ValidatePath(rule.Path, harEntry.Request.URL) || !ValidateService(rule.Service, service) {
continue
@@ -65,7 +69,7 @@ func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched {
if err != nil {
continue
}
fmt.Println(matchValue, rule.Value)
rlog.Info(matchValue, rule.Value)
} else {
val := fmt.Sprint(out)
matchValue, err = regexp.MatchString(rule.Value, val)
@@ -92,7 +96,7 @@ func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched {
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
}
}
return resultPolicyToSend
return
}
func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {

View File

@@ -4,13 +4,12 @@ import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/google/martian/har"
"github.com/romana/rlog"
"github.com/up9inc/mizu/tap/api"
"strconv"
"strings"
"time"
)
// Keep it because we might want cookies in the future

View File

@@ -1,8 +1,8 @@
package shared
package utils
import (
"fmt"
"github.com/gorilla/websocket"
"github.com/romana/rlog"
"time"
)
@@ -11,24 +11,23 @@ const (
DEFAULT_SOCKET_RETRY_SLEEP_TIME = time.Second * 10
)
func ConnectToSocketServer(address string, retries int, retrySleepTime time.Duration, hideTimeoutErrors bool) (*websocket.Conn, error) {
func ConnectToSocketServer(address string) (*websocket.Conn, error) {
var err error
var connection *websocket.Conn
try := 0
// Connection to server fails if client pod is up before server.
// Retries solve this issue.
for try < retries {
for try < DEFAULT_SOCKET_RETRIES {
rlog.Infof("Trying to connect to websocket: %s, attempt: %v/%v", address, try, DEFAULT_SOCKET_RETRIES)
connection, _, err = websocket.DefaultDialer.Dial(address, nil)
if err != nil {
rlog.Warnf("Failed connecting to websocket: %s, attempt: %v/%v, err: %s, (%v,%+v)", address, try, DEFAULT_SOCKET_RETRIES, err, err, err)
try++
if !hideTimeoutErrors {
fmt.Printf("Failed connecting to websocket server: %s, (%v,%+v)\n", err, err, err)
}
} else {
break
}
time.Sleep(retrySleepTime)
time.Sleep(DEFAULT_SOCKET_RETRY_SLEEP_TIME)
}
if err != nil {

View File

@@ -4,7 +4,6 @@ import (
"context"
"github.com/gin-gonic/gin"
"github.com/romana/rlog"
"log"
"net/http"
"net/url"
"os"
@@ -18,8 +17,8 @@ import (
func StartServer(app *gin.Engine) {
signals := make(chan os.Signal, 2)
signal.Notify(signals,
os.Interrupt, // this catch ctrl + c
syscall.SIGTSTP, // this catch ctrl + z
os.Interrupt, // this catch ctrl + c
syscall.SIGTSTP, // this catch ctrl + z
)
srv := &http.Server{
@@ -36,8 +35,9 @@ func StartServer(app *gin.Engine) {
}()
// Run server.
rlog.Infof("Starting the server...")
if err := app.Run(":8899"); err != nil {
log.Printf("Oops... Server is not running! Reason: %v", err)
rlog.Errorf("Server is not running! Reason: %v", err)
}
}
@@ -54,15 +54,14 @@ func ReverseSlice(data interface{}) {
func CheckErr(e error) {
if e != nil {
log.Printf("%v", e)
//panic(e)
rlog.Errorf("%v", e)
}
}
func SetHostname(address, newHostname string) string {
replacedUrl, err := url.Parse(address)
if err != nil{
log.Printf("error replacing hostname to %s in address %s, returning original %v",newHostname, address, err)
if err != nil {
rlog.Errorf("error replacing hostname to %s in address %s, returning original %v", newHostname, address, err)
return address
}
replacedUrl.Host = newHostname

View File

@@ -32,7 +32,7 @@ var logsCmd = &cobra.Command{
logger.Log.Debugf("Using file path %s", config.Config.Logs.FilePath())
if dumpLogsErr := fsUtils.DumpLogs(kubernetesProvider, ctx, config.Config.Logs.FilePath()); dumpLogsErr != nil {
if dumpLogsErr := fsUtils.DumpLogs(ctx, kubernetesProvider, config.Config.Logs.FilePath()); dumpLogsErr != nil {
logger.Log.Errorf("Failed dump logs %v", dumpLogsErr)
}

View File

@@ -67,7 +67,7 @@ func init() {
tapCmd.Flags().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables redaction of potentially sensitive request/response headers and body values")
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size")
tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file with policy rules")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
tapCmd.Flags().String(configStructs.EnforcePolicyFileDeprecated, defaultTapConfig.EnforcePolicyFileDeprecated, "Yaml file with policy rules")
tapCmd.Flags().MarkDeprecated(configStructs.EnforcePolicyFileDeprecated, fmt.Sprintf("Use --%s instead", configStructs.EnforcePolicyFile))

View File

@@ -8,6 +8,10 @@ import (
"strings"
"time"
"gopkg.in/yaml.v3"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/up9inc/mizu/cli/apiserver"
"github.com/up9inc/mizu/cli/config"
"github.com/up9inc/mizu/cli/config/configStructs"
@@ -22,9 +26,6 @@ import (
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/debounce"
"github.com/up9inc/mizu/tap/api"
yaml "gopkg.in/yaml.v3"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
)
const (
@@ -36,7 +37,6 @@ type tapState struct {
apiServerService *core.Service
currentlyTappedPods []core.Pod
mizuServiceAccountExists bool
doNotRemoveConfigMap bool
}
var state tapState
@@ -150,9 +150,6 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro
if err := createMizuConfigmap(ctx, kubernetesProvider, mizuValidationRules); 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\n", errormessage.FormatError(err)))
state.doNotRemoveConfigMap = true
} else if mizuValidationRules == "" {
state.doNotRemoveConfigMap = true
}
return nil
@@ -268,75 +265,108 @@ func finishMizuExecution(kubernetesProvider *kubernetes.Provider) {
telemetry.ReportAPICalls()
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
defer cancel()
dumpLogsIfNeeded(kubernetesProvider, removalCtx)
cleanUpMizuResources(kubernetesProvider, removalCtx, cancel)
dumpLogsIfNeeded(removalCtx, kubernetesProvider)
cleanUpMizuResources(removalCtx, cancel, kubernetesProvider)
}
func dumpLogsIfNeeded(kubernetesProvider *kubernetes.Provider, removalCtx context.Context) {
func dumpLogsIfNeeded(ctx context.Context, kubernetesProvider *kubernetes.Provider) {
if !config.Config.DumpLogs {
return
}
mizuDir := mizu.GetMizuFolderPath()
filePath := path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05")))
if err := fsUtils.DumpLogs(kubernetesProvider, removalCtx, filePath); err != nil {
if err := fsUtils.DumpLogs(ctx, kubernetesProvider, filePath); err != nil {
logger.Log.Errorf("Failed dump logs %v", err)
}
}
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider, removalCtx context.Context, cancel context.CancelFunc) {
func cleanUpMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
logger.Log.Infof("\nRemoving mizu resources\n")
if !config.Config.IsNsRestrictedMode() {
if err := kubernetesProvider.RemoveNamespace(removalCtx, config.Config.MizuResourcesNamespace); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Namespace %s: %v", config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
return
}
var leftoverResources []string
if config.Config.IsNsRestrictedMode() {
leftoverResources = cleanUpRestrictedMode(ctx, kubernetesProvider)
} else {
if err := kubernetesProvider.RemovePod(removalCtx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Pod %s in namespace %s: %v", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if err := kubernetesProvider.RemoveService(removalCtx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service %s in namespace %s: %v", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing DaemonSet %s in namespace %s: %v", mizu.TapperDaemonSetName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if !state.doNotRemoveConfigMap {
if err := kubernetesProvider.RemoveConfigMap(removalCtx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing ConfigMap %s in namespace %s: %v", mizu.ConfigMapName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
}
leftoverResources = cleanUpNonRestrictedMode(ctx, cancel, kubernetesProvider)
}
if state.mizuServiceAccountExists {
if !config.Config.IsNsRestrictedMode() {
if err := kubernetesProvider.RemoveNonNamespacedResources(removalCtx, mizu.ClusterRoleName, mizu.ClusterRoleBindingName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing non-namespaced resources: %v", errormessage.FormatError(err)))
return
}
} else {
if err := kubernetesProvider.RemoveServicAccount(removalCtx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service Account %s in namespace %s: %v", mizu.ServiceAccountName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
return
}
if err := kubernetesProvider.RemoveRole(removalCtx, config.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Role %s in namespace %s: %v", mizu.RoleName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if err := kubernetesProvider.RemoveRoleBinding(removalCtx, config.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing RoleBinding %s in namespace %s: %v", mizu.RoleBindingName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if len(leftoverResources) > 0 {
errMsg := fmt.Sprintf("Failed to remove the following resources, for more info check logs at %s:", logger.GetLogFilePath())
for _, resource := range leftoverResources {
errMsg += "\n- " + resource
}
logger.Log.Errorf(uiUtils.Error, errMsg)
}
}
func cleanUpRestrictedMode(ctx context.Context, kubernetesProvider *kubernetes.Provider) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemovePod(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Pod %s in namespace %s", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if !config.Config.IsNsRestrictedMode() {
waitUntilNamespaceDeleted(removalCtx, cancel, kubernetesProvider)
if err := kubernetesProvider.RemoveService(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Service %s in namespace %s", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveDaemonSet(ctx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
resourceDesc := fmt.Sprintf("DaemonSet %s in namespace %s", mizu.TapperDaemonSetName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveConfigMap(ctx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
resourceDesc := fmt.Sprintf("ConfigMap %s in namespace %s", mizu.ConfigMapName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveServicAccount(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
resourceDesc := fmt.Sprintf("Service Account %s in namespace %s", mizu.ServiceAccountName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRole(ctx, config.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
resourceDesc := fmt.Sprintf("Role %s in namespace %s", mizu.RoleName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRoleBinding(ctx, config.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", mizu.RoleBindingName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
return leftoverResources
}
func cleanUpNonRestrictedMode(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemoveNamespace(ctx, config.Config.MizuResourcesNamespace); err != nil {
resourceDesc := fmt.Sprintf("Namespace %s", config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
} else {
defer waitUntilNamespaceDeleted(ctx, cancel, kubernetesProvider)
}
if err := kubernetesProvider.RemoveClusterRole(ctx, mizu.ClusterRoleName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRole %s", mizu.ClusterRoleName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveClusterRoleBinding(ctx, mizu.ClusterRoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRoleBinding %s", mizu.ClusterRoleBindingName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
return leftoverResources
}
func handleDeletionError(err error, resourceDesc string, leftoverResources *[]string) {
logger.Log.Debugf("Error removing %s: %v", resourceDesc, errormessage.FormatError(err))
*leftoverResources = append(*leftoverResources, resourceDesc)
}
func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
@@ -536,7 +566,7 @@ func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
url := GetApiServerUrl()
if err := apiserver.Provider.InitAndTestConnection(url); err != nil {
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs")
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", logger.GetLogFilePath()))
cancel()
break
}

View File

@@ -47,7 +47,7 @@ func runMizuView() {
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil {
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs")
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", logger.GetLogFilePath()))
return
}

View File

@@ -16,7 +16,7 @@ const (
DisableRedactionTapName = "no-redact"
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
DryRunTapName = "dry-run"
EnforcePolicyFile = "traffic-validation"
EnforcePolicyFile = "traffic-validation-file"
EnforcePolicyFileDeprecated = "test-rules"
)
@@ -33,8 +33,8 @@ type TapConfig struct {
DisableRedaction bool `yaml:"no-redact" default:"false"`
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
DryRun bool `yaml:"dry-run" default:"false"`
EnforcePolicyFile string `yaml:"traffic-validation"`
EnforcePolicyFileDeprecated string `yaml:"test-rules"`
EnforcePolicyFile string `yaml:"traffic-validation-file"`
EnforcePolicyFileDeprecated string `yaml:"test-rules,omitempty" readonly:""`
ApiServerResources Resources `yaml:"api-server-resources"`
TapperResources Resources `yaml:"tapper-resources"`
}

View File

@@ -268,67 +268,21 @@ func (provider *Provider) CreateService(ctx context.Context, namespace string, s
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
}
func (provider *Provider) DoesServiceAccountExist(ctx context.Context, namespace string, serviceAccountName string) (bool, error) {
serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{})
return provider.doesResourceExist(serviceAccount, err)
}
func (provider *Provider) DoesConfigMapExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesNamespaceExist(ctx context.Context, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesClusterRoleExist(ctx context.Context, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().ClusterRoles().Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesClusterRoleBindingExist(ctx context.Context, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().ClusterRoleBindings().Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesRoleExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().Roles(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesRoleBindingExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().RoleBindings(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesPodExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesDaemonSetExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) doesResourceExist(resource interface{}, err error) (bool, error) {
var statusError *k8serrors.StatusError
if errors.As(err, &statusError) {
// expected behavior when resource does not exist
if statusError.ErrStatus.Reason == metav1.StatusReasonNotFound {
return false, nil
}
// Getting NotFound error is the expected behavior when a resource does not exist.
if k8serrors.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, err
}
return resource != nil, nil
}
@@ -441,115 +395,63 @@ func (provider *Provider) CreateMizuRBACNamespaceRestricted(ctx context.Context,
}
func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error {
if isFound, err := provider.DoesNamespaceExist(ctx, name); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
}
func (provider *Provider) RemoveNonNamespacedResources(ctx context.Context, clusterRoleName string, clusterRoleBindingName string) error {
if err := provider.RemoveClusterRole(ctx, clusterRoleName); err != nil {
return err
}
if err := provider.RemoveClusterRoleBinding(ctx, clusterRoleBindingName); err != nil {
return err
}
return nil
err := provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error {
if isFound, err := provider.DoesClusterRoleExist(ctx, name); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
err := provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error {
if isFound, err := provider.DoesClusterRoleBindingExist(ctx, name); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
err := provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveRoleBinding(ctx context.Context, namespace string, name string) error {
if isFound, err := provider.DoesRoleBindingExist(ctx, namespace, name); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().RoleBindings(namespace).Delete(ctx, name, metav1.DeleteOptions{})
err := provider.clientSet.RbacV1().RoleBindings(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveRole(ctx context.Context, namespace string, name string) error {
if isFound, err := provider.DoesRoleExist(ctx, namespace, name); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{})
err := provider.clientSet.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveServicAccount(ctx context.Context, namespace string, name string) error {
if isFound, err := provider.DoesServiceAccountExist(ctx, namespace, name); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error {
if isFound, err := provider.DoesPodExist(ctx, namespace, podName); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
err := provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveConfigMap(ctx context.Context, namespace string, configMapName string) error {
if isFound, err := provider.DoesConfigMapExist(ctx, namespace, configMapName); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().ConfigMaps(namespace).Delete(ctx, configMapName, metav1.DeleteOptions{})
err := provider.clientSet.CoreV1().ConfigMaps(namespace).Delete(ctx, configMapName, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error {
if isFound, err := provider.DoesServicesExist(ctx, namespace, serviceName); err != nil {
return err
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
err := provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error {
if isFound, err := provider.DoesDaemonSetExist(ctx, namespace, daemonSetName); err != nil {
return err
} else if !isFound {
err := provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
return provider.handleRemovalError(err)
}
func (provider *Provider) handleRemovalError(err error) error {
// Ignore NotFound - There is nothing to delete.
// Ignore Forbidden - Assume that a user could not have created the resource in the first place.
if k8serrors.IsNotFound(err) || k8serrors.IsForbidden(err) {
return nil
}
return provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
return err
}
func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string, configMapName string, data string) error {
@@ -731,7 +633,7 @@ func (provider *Provider) ListAllRunningPodsMatchingRegex(ctx context.Context, r
return matchingPods, nil
}
func (provider *Provider) GetPodLogs(namespace string, podName string, ctx context.Context) (string, error) {
func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string) (string, error) {
podLogOpts := core.PodLogOptions{}
req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
podLogs, err := req.Stream(ctx)
@@ -747,7 +649,7 @@ func (provider *Provider) GetPodLogs(namespace string, podName string, ctx conte
return str, nil
}
func (provider *Provider) GetNamespaceEvents(namespace string, ctx context.Context) (string, error) {
func (provider *Provider) GetNamespaceEvents(ctx context.Context, namespace string) (string, error) {
eventsOpts := metav1.ListOptions{}
eventList, err := provider.clientSet.CoreV1().Events(namespace).List(ctx, eventsOpts)
if err != nil {

View File

@@ -1,42 +0,0 @@
package mizu
import (
"encoding/json"
"github.com/gorilla/websocket"
"github.com/up9inc/mizu/shared"
core "k8s.io/api/core/v1"
"time"
)
type ControlSocket struct {
connection *websocket.Conn
}
func CreateControlSocket(socketServerAddress string) (*ControlSocket, error) {
connection, err := shared.ConnectToSocketServer(socketServerAddress, 30, 2 * time.Second, true)
if err != nil {
return nil, err
} else {
return &ControlSocket{connection: connection}, nil
}
}
func (controlSocket *ControlSocket) SendNewTappedPodsListMessage(pods []core.Pod) error {
podInfos := make([]shared.PodInfo, 0)
for _, pod := range pods {
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
}
tapStatus := shared.TapStatus{Pods: podInfos}
socketMessage := shared.CreateWebSocketStatusMessage(tapStatus)
jsonMessage, err := json.Marshal(socketMessage)
if err != nil {
return err
}
err = controlSocket.connection.WriteMessage(websocket.TextMessage, jsonMessage)
if err != nil {
return err
}
return nil
}

View File

@@ -12,7 +12,7 @@ import (
"regexp"
)
func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath string) error {
func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath string) error {
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{config.Config.MizuResourcesNamespace})
if err != nil {
@@ -32,7 +32,7 @@ func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath strin
defer zipWriter.Close()
for _, pod := range pods {
logs, err := provider.GetPodLogs(pod.Namespace, pod.Name, ctx)
logs, err := provider.GetPodLogs(ctx, pod.Namespace, pod.Name)
if err != nil {
logger.Log.Errorf("Failed to get logs, %v", err)
continue
@@ -47,7 +47,7 @@ func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath strin
}
}
events, err := provider.GetNamespaceEvents(config.Config.MizuResourcesNamespace, ctx)
events, err := provider.GetNamespaceEvents(ctx, config.Config.MizuResourcesNamespace)
if err != nil {
logger.Log.Debugf("Failed to get k8b events, %v", err)
} else {

View File

@@ -1,34 +1,31 @@
# API rules validation
# Traffic validation rules
This feature allows you to define set of simple rules, and test the API against them.
This feature allows you to define set of simple rules, and test the traffic against them.
Such validation may test response for specific JSON fields, headers, etc.
## Examples
Example 1: HTTP request (REST API call) that didnt pass validation is highlighted in red
Example 1: HTTP request (REST API call) that didn't pass validation is highlighted in red
![Simple UI](../assets/validation-example1.png)
- - -
Example 2: Details pane shows the validation rule details and whether it passed or failed
![Simple UI](../assets/validation-example2.png)
## How to use
To use this feature - create simple rules file (see details below) and pass this file as parameter to `mizu tap` command. For example, if rules are stored in file named `rules.yaml` — run the following command:
```shell
mizu tap --traffic-validation rules.yaml PODNAME
mizu tap --traffic-validation-file rules.yaml
```
## Rules file structure
The structure of the traffic-validation-file is:
@@ -62,6 +59,7 @@ rules:
service: "carts.*"
```
### Explanation:
* First rule `holy-in-name-property`:
@@ -74,5 +72,4 @@ rules:
* Third rule `latency-test`:
> This rule will be applied to all request made to `carts.*` services. If the latency of the response is greater than `1` will be marked as failure, marked as success otherwise.
> This rule will be applied to all request made to `carts.*` services. If the latency of the response is greater than `1ms` will be marked as failure, marked as success otherwise.

View File

@@ -106,7 +106,7 @@ type MizuEntry struct {
UpdatedAt time.Time
ProtocolName string `json:"protocolName" gorm:"column:protocolName"`
ProtocolLongName string `json:"protocolLongName" gorm:"column:protocolLongName"`
ProtocolAbbreviation string `json:"protocolAbbreviation" gorm:"column:protocolVersion"`
ProtocolAbbreviation string `json:"protocolAbbreviation" gorm:"column:protocolAbbreviation"`
ProtocolVersion string `json:"protocolVersion" gorm:"column:protocolVersion"`
ProtocolBackgroundColor string `json:"protocolBackgroundColor" gorm:"column:protocolBackgroundColor"`
ProtocolForegroundColor string `json:"protocolForegroundColor" gorm:"column:protocolForegroundColor"`
@@ -138,6 +138,7 @@ type MizuEntryWrapper struct {
BodySize int64 `json:"bodySize"`
Data MizuEntry `json:"data"`
Rules []map[string]interface{} `json:"rulesMatched,omitempty"`
IsRulesEnabled bool `json:"isRulesEnabled"`
}
type BaseEntryDetails struct {
@@ -156,7 +157,7 @@ type BaseEntryDetails struct {
SourcePort string `json:"sourcePort,omitempty"`
DestinationPort string `json:"destinationPort,omitempty"`
IsOutgoing bool `json:"isOutgoing,omitempty"`
Latency int64 `json:"latency,omitempty"`
Latency int64 `json:"latency"`
Rules ApplicableRules `json:"rules,omitempty"`
}
@@ -190,6 +191,7 @@ func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
bed.Timestamp = entry.Timestamp
bed.RequestSenderIp = entry.RequestSenderIp
bed.IsOutgoing = entry.IsOutgoing
bed.Latency = entry.ElapsedTime
return nil
}

View File

@@ -312,7 +312,7 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing,
Latency: 0,
Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{
Latency: 0,
Status: false,

View File

@@ -223,7 +223,7 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing,
Latency: 0,
Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{
Latency: 0,
Status: false,

View File

@@ -86,6 +86,10 @@ func getContentTypeHeaderValue(headers http.Header) string {
}
func isFieldNameSensitive(fieldName string) bool {
if fieldName == ":authority" {
return false
}
name := strings.ToLower(fieldName)
name = strings.ReplaceAll(name, "_", "")
name = strings.ReplaceAll(name, "-", "")

View File

@@ -186,7 +186,7 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing,
Latency: 0,
Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{
Latency: 0,
Status: false,

View File

@@ -0,0 +1,14 @@
package main
//ConnectError redis connection error,such as io timeout
type ConnectError struct {
Message string
}
func newConnectError(message string) *ConnectError {
return &ConnectError{Message: message}
}
func (e *ConnectError) Error() string {
return e.Message
}

View File

@@ -0,0 +1,9 @@
module github.com/up9inc/mizu/tap/extensions/redis
go 1.16
require (
github.com/up9inc/mizu/tap/api v0.0.0
)
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../../api

View File

@@ -0,0 +1,55 @@
package main
import (
"fmt"
"github.com/up9inc/mizu/tap/api"
)
func handleClientStream(tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, request *RedisPacket) error {
counterPair.Request++
ident := fmt.Sprintf(
"%s->%s %s->%s %d",
tcpID.SrcIP,
tcpID.DstIP,
tcpID.SrcPort,
tcpID.DstPort,
counterPair.Request,
)
item := reqResMatcher.registerRequest(ident, request, superTimer.CaptureTime)
if item != nil {
item.ConnectionInfo = &api.ConnectionInfo{
ClientIP: tcpID.SrcIP,
ClientPort: tcpID.SrcPort,
ServerIP: tcpID.DstIP,
ServerPort: tcpID.DstPort,
IsOutgoing: true,
}
emitter.Emit(item)
}
return nil
}
func handleServerStream(tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, response *RedisPacket) error {
counterPair.Response++
ident := fmt.Sprintf(
"%s->%s %s->%s %d",
tcpID.DstIP,
tcpID.SrcIP,
tcpID.DstPort,
tcpID.SrcPort,
counterPair.Response,
)
item := reqResMatcher.registerResponse(ident, response, superTimer.CaptureTime)
if item != nil {
item.ConnectionInfo = &api.ConnectionInfo{
ClientIP: tcpID.DstIP,
ClientPort: tcpID.DstPort,
ServerIP: tcpID.SrcIP,
ServerPort: tcpID.SrcPort,
IsOutgoing: false,
}
emitter.Emit(item)
}
return nil
}

View File

@@ -0,0 +1,57 @@
package main
import (
"encoding/json"
"github.com/up9inc/mizu/tap/api"
)
type RedisPayload struct {
Data interface{}
}
type RedisPayloader interface {
MarshalJSON() ([]byte, error)
}
func (h RedisPayload) MarshalJSON() ([]byte, error) {
return json.Marshal(h.Data)
}
type RedisWrapper struct {
Method string `json:"method"`
Url string `json:"url"`
Details interface{} `json:"details"`
}
func representGeneric(generic map[string]string) (representation []interface{}) {
details, _ := json.Marshal([]map[string]string{
{
"name": "Type",
"value": generic["type"],
},
{
"name": "Command",
"value": generic["command"],
},
{
"name": "Key",
"value": generic["key"],
},
{
"name": "Value",
"value": generic["value"],
},
{
"name": "Keyword",
"value": generic["keyword"],
},
})
representation = append(representation, map[string]string{
"type": api.TABLE,
"title": "Details",
"data": string(details),
})
return
}

View File

@@ -0,0 +1,154 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"github.com/up9inc/mizu/tap/api"
)
var protocol api.Protocol = api.Protocol{
Name: "redis",
LongName: "Redis Serialization Protocol",
Abbreviation: "REDIS",
Version: "3.x",
BackgroundColor: "#a41e11",
ForegroundColor: "#ffffff",
FontSize: 11,
ReferenceLink: "https://redis.io/topics/protocol",
Ports: []string{"6379"},
Priority: 3,
}
func init() {
log.Println("Initializing Redis extension...")
}
type dissecting string
func (d dissecting) Register(extension *api.Extension) {
extension.Protocol = &protocol
extension.MatcherMap = reqResMatcher.openMessagesMap
}
func (d dissecting) Ping() {
log.Printf("pong %s\n", protocol.Name)
}
func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, superIdentifier *api.SuperIdentifier, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
is := &RedisInputStream{
Reader: b,
Buf: make([]byte, 8192),
}
proto := NewProtocol(is)
for {
redisPacket, err := proto.Read()
if err != nil {
return err
}
if isClient {
handleClientStream(tcpID, counterPair, superTimer, emitter, redisPacket)
} else {
handleServerStream(tcpID, counterPair, superTimer, emitter, redisPacket)
}
}
}
func (d dissecting) Analyze(item *api.OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *api.MizuEntry {
request := item.Pair.Request.Payload.(map[string]interface{})
reqDetails := request["details"].(map[string]interface{})
service := "redis"
if resolvedDestination != "" {
service = resolvedDestination
} else if resolvedSource != "" {
service = resolvedSource
}
method := ""
if reqDetails["command"] != nil {
method = reqDetails["command"].(string)
}
summary := ""
if reqDetails["key"] != nil {
summary = reqDetails["key"].(string)
}
request["url"] = summary
entryBytes, _ := json.Marshal(item.Pair)
return &api.MizuEntry{
ProtocolName: protocol.Name,
ProtocolLongName: protocol.LongName,
ProtocolAbbreviation: protocol.Abbreviation,
ProtocolVersion: protocol.Version,
ProtocolBackgroundColor: protocol.BackgroundColor,
ProtocolForegroundColor: protocol.ForegroundColor,
ProtocolFontSize: protocol.FontSize,
ProtocolReferenceLink: protocol.ReferenceLink,
EntryId: entryId,
Entry: string(entryBytes),
Url: fmt.Sprintf("%s%s", service, summary),
Method: method,
Status: 0,
RequestSenderIp: item.ConnectionInfo.ClientIP,
Service: service,
Timestamp: item.Timestamp,
ElapsedTime: 0,
Path: summary,
ResolvedSource: resolvedSource,
ResolvedDestination: resolvedDestination,
SourceIp: item.ConnectionInfo.ClientIP,
DestinationIp: item.ConnectionInfo.ServerIP,
SourcePort: item.ConnectionInfo.ClientPort,
DestinationPort: item.ConnectionInfo.ServerPort,
IsOutgoing: item.ConnectionInfo.IsOutgoing,
}
}
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
return &api.BaseEntryDetails{
Id: entry.EntryId,
Protocol: protocol,
Url: entry.Url,
RequestSenderIp: entry.RequestSenderIp,
Service: entry.Service,
Summary: entry.Path,
StatusCode: entry.Status,
Method: entry.Method,
Timestamp: entry.Timestamp,
SourceIp: entry.SourceIp,
DestinationIp: entry.DestinationIp,
SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing,
Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{
Latency: 0,
Status: false,
},
}
}
func (d dissecting) Represent(entry *api.MizuEntry) (p api.Protocol, object []byte, bodySize int64, err error) {
p = protocol
bodySize = 0
var root map[string]interface{}
json.Unmarshal([]byte(entry.Entry), &root)
representation := make(map[string]interface{}, 0)
request := root["request"].(map[string]interface{})["payload"].(map[string]interface{})
response := root["response"].(map[string]interface{})["payload"].(map[string]interface{})
reqDetails := request["details"].(map[string]string)
resDetails := response["details"].(map[string]string)
repRequest := representGeneric(reqDetails)
repResponse := representGeneric(resDetails)
representation["request"] = repRequest
representation["response"] = repResponse
object, err = json.Marshal(representation)
return
}
var Dissector dissecting

View File

@@ -0,0 +1,102 @@
package main
import (
"fmt"
"strings"
"sync"
"time"
"github.com/up9inc/mizu/tap/api"
)
var reqResMatcher = createResponseRequestMatcher() // global
// Key is {client_addr}:{client_port}->{dest_addr}:{dest_port}_{incremental_counter}
type requestResponseMatcher struct {
openMessagesMap *sync.Map
}
func createResponseRequestMatcher() requestResponseMatcher {
newMatcher := &requestResponseMatcher{openMessagesMap: &sync.Map{}}
return *newMatcher
}
func (matcher *requestResponseMatcher) registerRequest(ident string, request *RedisPacket, captureTime time.Time) *api.OutputChannelItem {
split := splitIdent(ident)
key := genKey(split)
requestRedisMessage := api.GenericMessage{
IsRequest: true,
CaptureTime: captureTime,
Payload: RedisPayload{
Data: &RedisWrapper{
Method: string(request.Command),
Url: "",
Details: request,
},
},
}
if response, found := matcher.openMessagesMap.LoadAndDelete(key); found {
// Type assertion always succeeds because all of the map's values are of api.GenericMessage type
responseRedisMessage := response.(*api.GenericMessage)
if responseRedisMessage.IsRequest {
return nil
}
return matcher.preparePair(&requestRedisMessage, responseRedisMessage)
}
matcher.openMessagesMap.Store(key, &requestRedisMessage)
return nil
}
func (matcher *requestResponseMatcher) registerResponse(ident string, response *RedisPacket, captureTime time.Time) *api.OutputChannelItem {
split := splitIdent(ident)
key := genKey(split)
responseRedisMessage := api.GenericMessage{
IsRequest: false,
CaptureTime: captureTime,
Payload: RedisPayload{
Data: &RedisWrapper{
Method: string(response.Command),
Url: "",
Details: response,
},
},
}
if request, found := matcher.openMessagesMap.LoadAndDelete(key); found {
// Type assertion always succeeds because all of the map's values are of api.GenericMessage type
requestRedisMessage := request.(*api.GenericMessage)
if !requestRedisMessage.IsRequest {
return nil
}
return matcher.preparePair(requestRedisMessage, &responseRedisMessage)
}
matcher.openMessagesMap.Store(key, &responseRedisMessage)
return nil
}
func (matcher *requestResponseMatcher) preparePair(requestRedisMessage *api.GenericMessage, responseRedisMessage *api.GenericMessage) *api.OutputChannelItem {
return &api.OutputChannelItem{
Protocol: protocol,
Timestamp: requestRedisMessage.CaptureTime.UnixNano() / int64(time.Millisecond),
ConnectionInfo: nil,
Pair: &api.RequestResponsePair{
Request: *requestRedisMessage,
Response: *responseRedisMessage,
},
}
}
func splitIdent(ident string) []string {
ident = strings.Replace(ident, "->", " ", -1)
return strings.Split(ident, " ")
}
func genKey(split []string) string {
key := fmt.Sprintf("%s:%s->%s:%s,%s", split[0], split[2], split[1], split[3], split[4])
return key
}

View File

@@ -0,0 +1,474 @@
package main
import (
"bufio"
"errors"
"fmt"
"log"
"math"
"reflect"
"strconv"
"strings"
"time"
)
const (
askPrefix = "ASK "
movedPrefix = "MOVED "
clusterDownPrefix = "CLUSTERDOWN "
busyPrefix = "BUSY "
noscriptPrefix = "NOSCRIPT "
defaultHost = "localhost"
defaultPort = 6379
defaultSentinelPort = 26379
defaultTimeout = 5 * time.Second
defaultDatabase = 2 * time.Second
dollarByte = '$'
asteriskByte = '*'
plusByte = '+'
minusByte = '-'
colonByte = ':'
notApplicableByte = '0'
sentinelMasters = "masters"
sentinelGetMasterAddrByName = "get-master-addr-by-name"
sentinelReset = "reset"
sentinelSlaves = "slaves"
sentinelFailOver = "failover"
sentinelMonitor = "monitor"
sentinelRemove = "remove"
sentinelSet = "set"
clusterNodes = "nodes"
clusterMeet = "meet"
clusterReset = "reset"
clusterAddSlots = "addslots"
clusterDelSlots = "delslots"
clusterInfo = "info"
clusterGetKeysInSlot = "getkeysinslot"
clusterSetSlot = "setslot"
clusterSetSlotNode = "node"
clusterSetSlotMigrating = "migrating"
clusterSetSlotImporting = "importing"
clusterSetSlotStable = "stable"
clusterForget = "forget"
clusterFlushSlot = "flushslots"
clusterKeySlot = "keyslot"
clusterCountKeyInSlot = "countkeysinslot"
clusterSaveConfig = "saveconfig"
clusterReplicate = "replicate"
clusterSlaves = "slaves"
clusterFailOver = "failover"
clusterSlots = "slots"
pubSubChannels = "channels"
pubSubNumSub = "numsub"
pubSubNumPat = "numpat"
)
//intToByteArr convert int to byte array
func intToByteArr(a int) []byte {
buf := make([]byte, 0)
return strconv.AppendInt(buf, int64(a), 10)
}
var (
bytesTrue = intToByteArr(1)
bytesFalse = intToByteArr(0)
bytesTilde = []byte("~")
positiveInfinityBytes = []byte("+inf")
negativeInfinityBytes = []byte("-inf")
)
var (
sizeTable = []int{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999,
999999999, math.MaxInt32}
digitTens = []byte{'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4',
'4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6',
'6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8',
'8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'}
digitOnes = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
digits = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z'}
)
// receive message from redis
type RedisInputStream struct {
*bufio.Reader
Buf []byte
count int
limit int
}
func (r *RedisInputStream) readByte() (byte, error) {
err := r.ensureFill()
if err != nil {
return 0, err
}
ret := r.Buf[r.count]
r.count++
return ret, nil
}
func (r *RedisInputStream) ensureFill() error {
if r.count < r.limit {
return nil
}
var err error
r.limit, err = r.Read(r.Buf)
if err != nil {
return newConnectError(err.Error())
}
r.count = 0
if r.limit == -1 {
return newConnectError("Unexpected end of stream")
}
return nil
}
func (r *RedisInputStream) readLine() (string, error) {
buf := ""
for {
err := r.ensureFill()
if err != nil {
return "", err
}
b := r.Buf[r.count]
r.count++
if b == '\r' {
err := r.ensureFill()
if err != nil {
return "", err
}
c := r.Buf[r.count]
r.count++
if c == '\n' {
break
}
buf += string(b)
buf += string(c)
} else {
buf += string(b)
}
}
if buf == "" {
return "", newConnectError("It seems like server has closed the connection.")
}
return buf, nil
}
func (r *RedisInputStream) readLineBytes() ([]byte, error) {
err := r.ensureFill()
if err != nil {
return nil, err
}
pos := r.count
buf := r.Buf
for {
if pos == r.limit {
return r.readLineBytesSlowly()
}
p := buf[pos]
pos++
if p == '\r' {
if pos == r.limit {
return r.readLineBytesSlowly()
}
p := buf[pos]
pos++
if p == '\n' {
break
}
}
}
N := pos - r.count - 2
line := make([]byte, N)
j := 0
for i := r.count; i <= N; i++ {
line[j] = buf[i]
j++
}
r.count = pos
return line, nil
}
func (r *RedisInputStream) readLineBytesSlowly() ([]byte, error) {
buf := make([]byte, 0)
for {
err := r.ensureFill()
if err != nil {
return nil, err
}
b := r.Buf[r.count]
r.count++
if b == 'r' {
err := r.ensureFill()
if err != nil {
return nil, err
}
c := r.Buf[r.count]
r.count++
if c == '\n' {
break
}
buf = append(buf, b)
buf = append(buf, c)
} else {
buf = append(buf, b)
}
}
return buf, nil
}
func (r *RedisInputStream) readIntCrLf() (int64, error) {
err := r.ensureFill()
if err != nil {
return 0, err
}
buf := r.Buf
isNeg := false
if buf[r.count] == '-' {
isNeg = true
}
if isNeg {
r.count++
}
value := int64(0)
for {
err := r.ensureFill()
if err != nil {
return 0, err
}
b := buf[r.count]
r.count++
if b == '\r' {
err := r.ensureFill()
if err != nil {
return 0, err
}
c := buf[r.count]
r.count++
if c != '\n' {
return 0, newConnectError("Unexpected character!")
}
break
} else {
value = value*10 + int64(b) - int64('0')
}
}
if isNeg {
return -value, nil
}
return value, nil
}
type RedisProtocol struct {
is *RedisInputStream
}
func NewProtocol(is *RedisInputStream) *RedisProtocol {
return &RedisProtocol{
is: is,
}
}
func (p *RedisProtocol) Read() (packet *RedisPacket, err error) {
x, r, err := p.process()
if err != nil {
return
}
packet = &RedisPacket{}
packet.Type = r
switch x.(type) {
case []interface{}:
array := x.([]interface{})
packet.Command = RedisCommand(strings.ToUpper(string(array[0].([]uint8))))
if len(array) > 1 {
packet.Key = string(array[1].([]uint8))
}
if len(array) > 2 {
packet.Value = string(array[2].([]uint8))
}
case []uint8:
val := string(x.([]uint8))
if packet.Type == types[plusByte] {
packet.Keyword = RedisKeyword(strings.ToUpper(val))
if !isValidRedisKeyword(keywords, packet.Keyword) {
err = errors.New(fmt.Sprintf("Unrecognized keyword: %s", string(packet.Command)))
return
}
} else {
packet.Value = val
}
case string:
packet.Value = x.(string)
default:
msg := fmt.Sprintf("Unrecognized Redis data type: %v\n", reflect.TypeOf(x))
log.Printf(msg)
err = errors.New(msg)
return
}
if packet.Command != "" {
if !isValidRedisCommand(commands, packet.Command) {
err = errors.New(fmt.Sprintf("Unrecognized command: %s", string(packet.Command)))
return
}
}
return
}
func (p *RedisProtocol) process() (v interface{}, r RedisType, err error) {
b, err := p.is.readByte()
if err != nil {
return nil, types[notApplicableByte], newConnectError(err.Error())
}
switch b {
case plusByte:
v, err = p.processSimpleString()
r = types[plusByte]
return
case dollarByte:
v, err = p.processBulkString()
r = types[dollarByte]
return
case asteriskByte:
v, err = p.processArray()
r = types[asteriskByte]
return
case colonByte:
v, err = p.processInteger()
r = types[colonByte]
return
case minusByte:
v, err = p.processError()
r = types[minusByte]
return
default:
return nil, types[notApplicableByte], newConnectError(fmt.Sprintf("Unknown reply: %b", b))
}
}
func (p *RedisProtocol) processSimpleString() ([]byte, error) {
return p.is.readLineBytes()
}
func (p *RedisProtocol) processBulkString() ([]byte, error) {
l, err := p.is.readIntCrLf()
if err != nil {
return nil, newConnectError(err.Error())
}
if l == -1 {
return nil, nil
}
line := make([]byte, 0)
for {
err := p.is.ensureFill()
if err != nil {
return nil, err
}
b := p.is.Buf[p.is.count]
p.is.count++
if b == '\r' {
err := p.is.ensureFill()
if err != nil {
return nil, err
}
c := p.is.Buf[p.is.count]
p.is.count++
if c != '\n' {
return nil, newConnectError("Unexpected character!")
}
break
} else {
line = append(line, b)
}
}
return line, nil
}
func (p *RedisProtocol) processArray() ([]interface{}, error) {
l, err := p.is.readIntCrLf()
if err != nil {
return nil, newConnectError(err.Error())
}
if l == -1 {
return nil, nil
}
ret := make([]interface{}, 0)
for i := 0; i < int(l); i++ {
if obj, _, err := p.process(); err != nil {
ret = append(ret, err)
} else {
ret = append(ret, obj)
}
}
return ret, nil
}
func (p *RedisProtocol) processInteger() (int64, error) {
return p.is.readIntCrLf()
}
func (p *RedisProtocol) processError() (interface{}, error) {
msg, err := p.is.readLine()
if err != nil {
return nil, newConnectError(err.Error())
}
if strings.HasPrefix(msg, movedPrefix) {
host, port, slot, err := p.parseTargetHostAndSlot(msg)
if err != nil {
return nil, err
}
return fmt.Sprintf("MovedDataError: %s host: %s port: %d slot: %d", msg, host, port, slot), nil
} else if strings.HasPrefix(msg, askPrefix) {
host, port, slot, err := p.parseTargetHostAndSlot(msg)
if err != nil {
return nil, err
}
return fmt.Sprintf("AskDataError: %s host: %s port: %d slot: %d", msg, host, port, slot), nil
} else if strings.HasPrefix(msg, clusterDownPrefix) {
return fmt.Sprintf("ClusterError: %s", msg), nil
} else if strings.HasPrefix(msg, busyPrefix) {
return fmt.Sprintf("BusyError: %s", msg), nil
} else if strings.HasPrefix(msg, noscriptPrefix) {
return fmt.Sprintf("NoScriptError: %s", msg), nil
}
return fmt.Sprintf("DataError: %s", msg), nil
}
func (p *RedisProtocol) parseTargetHostAndSlot(clusterRedirectResponse string) (host string, po int, slot int, err error) {
arr := strings.Split(clusterRedirectResponse, " ")
host, port := p.extractParts(arr[2])
slot, err = strconv.Atoi(arr[1])
po, err = strconv.Atoi(port)
return
}
func (p *RedisProtocol) extractParts(from string) (string, string) {
idx := strings.LastIndex(from, ":")
host := from
if idx != -1 {
host = from[0:idx]
}
port := ""
if idx != -1 {
port = from[idx+1:]
}
return host, port
}

View File

@@ -0,0 +1,290 @@
package main
type RedisType string
type RedisCommand string
type RedisKeyword string
var types map[rune]RedisType = map[rune]RedisType{
plusByte: "Simple String",
dollarByte: "Bulk String",
asteriskByte: "Array",
colonByte: "Integer",
minusByte: "Error",
notApplicableByte: "N/A",
}
var commands []RedisCommand = []RedisCommand{
"PING",
"SET",
"GET",
"QUIT",
"EXISTS",
"DEL",
"UNLINK",
"TYPE",
"FLUSHDB",
"KEYS",
"RANDOMKEY",
"RENAME",
"RENAMENX",
"RENAMEX",
"DBSIZE",
"EXPIRE",
"EXPIREAT",
"TTL",
"SELECT",
"MOVE",
"FLUSHALL",
"GETSET",
"MGET",
"SETNX",
"SETEX",
"MSET",
"MSETNX",
"DECRBY",
"DECR",
"INCRBY",
"INCR",
"APPEND",
"SUBSTR",
"HSET",
"HGET",
"HSETNX",
"HMSET",
"HMGET",
"HINCRBY",
"HEXISTS",
"HDEL",
"HLEN",
"HKEYS",
"HVALS",
"HGETALL",
"RPUSH",
"LPUSH",
"LLEN",
"LRANGE",
"LTRIM",
"LINDEX",
"LSET",
"LREM",
"LPOP",
"RPOP",
"RPOPLPUSH",
"SADD",
"SMEMBERS",
"SREM",
"SPOP",
"SMOVE",
"SCARD",
"SISMEMBER",
"SINTER",
"SINTERSTORE",
"SUNION",
"SUNIONSTORE",
"SDIFF",
"SDIFFSTORE",
"SRANDMEMBER",
"ZADD",
"ZRANGE",
"ZREM",
"ZINCRBY",
"ZRANK",
"ZREVRANK",
"ZREVRANGE",
"ZCARD",
"ZSCORE",
"MULTI",
"DISCARD",
"EXEC",
"WATCH",
"UNWATCH",
"SORT",
"BLPOP",
"BRPOP",
"AUTH",
"SUBSCRIBE",
"PUBLISH",
"UNSUBSCRIBE",
"PSUBSCRIBE",
"PUNSUBSCRIBE",
"PUBSUB",
"ZCOUNT",
"ZRANGEBYSCORE",
"ZREVRANGEBYSCORE",
"ZREMRANGEBYRANK",
"ZREMRANGEBYSCORE",
"ZUNIONSTORE",
"ZINTERSTORE",
"ZLEXCOUNT",
"ZRANGEBYLEX",
"ZREVRANGEBYLEX",
"ZREMRANGEBYLEX",
"SAVE",
"BGSAVE",
"BGREWRITEAOF",
"LASTSAVE",
"SHUTDOWN",
"INFO",
"MONITOR",
"SLAVEOF",
"CONFIG",
"STRLEN",
"SYNC",
"LPUSHX",
"PERSIST",
"RPUSHX",
"ECHO",
"LINSERT",
"DEBUG",
"BRPOPLPUSH",
"SETBIT",
"GETBIT",
"BITPOS",
"SETRANGE",
"GETRANGE",
"EVAL",
"EVALSHA",
"SCRIPT",
"SLOWLOG",
"OBJECT",
"BITCOUNT",
"BITOP",
"SENTINEL",
"DUMP",
"RESTORE",
"PEXPIRE",
"PEXPIREAT",
"PTTL",
"INCRBYFLOAT",
"PSETEX",
"CLIENT",
"TIME",
"MIGRATE",
"HINCRBYFLOAT",
"SCAN",
"HSCAN",
"SSCAN",
"ZSCAN",
"WAIT",
"CLUSTER",
"ASKING",
"PFADD",
"PFCOUNT",
"PFMERGE",
"READONLY",
"GEOADD",
"GEODIST",
"GEOHASH",
"GEOPOS",
"GEORADIUS",
"GEORADIUS_RO",
"GEORADIUSBYMEMBER",
"GEORADIUSBYMEMBER_RO",
"MODULE",
"BITFIELD",
"HSTRLEN",
"TOUCH",
"SWAPDB",
"MEMORY",
"XADD",
"XLEN",
"XDEL",
"XTRIM",
"XRANGE",
"XREVRANGE",
"XREAD",
"XACK",
"XGROUP",
"XREADGROUP",
"XPENDING",
"XCLAIM",
}
var keywords []RedisKeyword = []RedisKeyword{
"AGGREGATE",
"ALPHA",
"ASC",
"BY",
"DESC",
"GET",
"LIMIT",
"MESSAGE",
"NO",
"NOSORT",
"PMESSAGE",
"PSUBSCRIBE",
"PUNSUBSCRIBE",
"OK",
"ONE",
"QUEUED",
"SET",
"STORE",
"SUBSCRIBE",
"UNSUBSCRIBE",
"WEIGHTS",
"WITHSCORES",
"RESETSTAT",
"REWRITE",
"RESET",
"FLUSH",
"EXISTS",
"LOAD",
"KILL",
"LEN",
"REFCOUNT",
"ENCODING",
"IDLETIME",
"GETNAME",
"SETNAME",
"LIST",
"MATCH",
"COUNT",
"PING",
"PONG",
"UNLOAD",
"REPLACE",
"KEYS",
"PAUSE",
"DOCTOR",
"BLOCK",
"NOACK",
"STREAMS",
"KEY",
"CREATE",
"MKSTREAM",
"SETID",
"DESTROY",
"DELCONSUMER",
"MAXLEN",
"GROUP",
"IDLE",
"TIME",
"RETRYCOUNT",
"FORCE",
}
type RedisPacket struct {
Type RedisType `json:"type"`
Command RedisCommand `json:"command"`
Key string `json:"key"`
Value string `json:"value"`
Keyword RedisKeyword `json:"keyword"`
}
func isValidRedisCommand(s []RedisCommand, c RedisCommand) bool {
for _, v := range s {
if v == c {
return true
}
}
return false
}
func isValidRedisKeyword(s []RedisKeyword, c RedisKeyword) bool {
for _, v := range s {
if v == c {
return true
}
}
return false
}

6
ui/package-lock.json generated
View File

@@ -13454,9 +13454,9 @@
}
},
"react-scrollable-feed-virtualized": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/react-scrollable-feed-virtualized/-/react-scrollable-feed-virtualized-1.4.2.tgz",
"integrity": "sha512-j6M80ETqhSRBlygWe491gMPqCiAkVUQsLd/JR7DCKsZF44IYlJiunZWjdWe3//gxddTL68pjqq8pMvd1YN1bgw=="
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/react-scrollable-feed-virtualized/-/react-scrollable-feed-virtualized-1.4.3.tgz",
"integrity": "sha512-M9WgJKr57jCyWKNCksc3oi+xhtO0YbL9d7Ll8Sdc5ZWOIstNvdNbNX0k4Nq6kXUVaHCJ9qE8omdSI/CxT3MLAQ=="
},
"react-syntax-highlighter": {
"version": "15.4.3",

View File

@@ -21,7 +21,7 @@
"react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"react-scrollable-feed-virtualized": "^1.4.2",
"react-scrollable-feed-virtualized": "^1.4.3",
"react-syntax-highlighter": "^15.4.3",
"typescript": "^4.2.4",
"web-vitals": "^1.1.1"

View File

@@ -91,7 +91,7 @@ const App = () => {
</table>
</span>
return (
<div className="mizuApp">
<div className="header">

View File

@@ -41,7 +41,7 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime}) => {
<Protocol protocol={protocol} horizontal={true}/>
<div style={{right: "30px", position: "absolute", display: "flex"}}>
{response.payload && <div style={{margin: "0 18px", opacity: 0.5}}>{formatSize(bodySize)}</div>}
<div style={{marginRight: 18, opacity: 0.5}}>{Math.round(elapsedTime)}ms</div>
{response.payload && <div style={{marginRight: 18, opacity: 0.5}}>{Math.round(elapsedTime)}ms</div>}
</div>
</div>;
};
@@ -71,7 +71,7 @@ export const EntryDetailed: React.FC<EntryDetailedProps> = ({entryData}) => {
/>
{entryData.data && <EntrySummary data={entryData.data}/>}
<>
{entryData.data && <EntryViewer representation={entryData.representation} rulesMatched={entryData.rulesMatched} elapsedTime={entryData.data.elapsedTime} color={entryData.protocol.backgroundColor}/>}
{entryData.data && <EntryViewer representation={entryData.representation} isRulesEnabled={entryData.isRulesEnabled} rulesMatched={entryData.rulesMatched} elapsedTime={entryData.data.elapsedTime} color={entryData.protocol.backgroundColor}/>}
</>
</>
};

View File

@@ -153,10 +153,8 @@ export const EntryTableSection: React.FC<EntrySectionProps> = ({title, color, ar
interface EntryPolicySectionProps {
service: string,
title: string,
color: string,
response: any,
latency?: number,
arrayToIterate: any[],
}
@@ -200,7 +198,7 @@ export const EntryPolicySectionContainer: React.FC<EntryPolicySectionContainerPr
</CollapsibleContainer>
}
export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({service, title, color, response, latency, arrayToIterate}) => {
export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({title, color, latency, arrayToIterate}) => {
return <React.Fragment>
{
arrayToIterate && arrayToIterate.length > 0 ?

View File

@@ -33,8 +33,8 @@ const SectionsRepresentation: React.FC<any> = ({data, color}) => {
return <>{sections}</>;
}
const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapsedTime, color}) => {
const TABS = [
const AutoRepresentation: React.FC<any> = ({representation, isRulesEnabled, rulesMatched, elapsedTime, color}) => {
var TABS = [
{
tab: 'request'
},
@@ -54,6 +54,14 @@ const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapse
const {request, response} = JSON.parse(representation);
if (!response) {
TABS[1]['hidden'] = true;
}
if (!isRulesEnabled) {
TABS.pop()
}
return <div className={styles.Entry}>
{<div className={styles.body}>
<div className={styles.bodyHeader}>
@@ -63,11 +71,11 @@ const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapse
{currentTab === TABS[0].tab && <React.Fragment>
<SectionsRepresentation data={request} color={color}/>
</React.Fragment>}
{currentTab === TABS[1].tab && <React.Fragment>
{response && currentTab === TABS[1].tab && <React.Fragment>
<SectionsRepresentation data={response} color={color}/>
</React.Fragment>}
{currentTab === TABS[2].tab && <React.Fragment>
<EntryTablePolicySection service={representation.service} title={'Rule'} color={color} latency={elapsedTime} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>
{TABS.length > 2 && currentTab === TABS[2].tab && <React.Fragment>
<EntryTablePolicySection title={'Rule'} color={color} latency={elapsedTime} arrayToIterate={rulesMatched ? rulesMatched : []}/>
</React.Fragment>}
</div>}
</div>;
@@ -75,13 +83,14 @@ const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapse
interface Props {
representation: any;
isRulesEnabled: boolean;
rulesMatched: any;
color: string;
elapsedTime: number;
}
const EntryViewer: React.FC<Props> = ({representation, rulesMatched, elapsedTime, color}) => {
return <AutoRepresentation representation={representation} rulesMatched={rulesMatched} elapsedTime={elapsedTime} color={color}/>
const EntryViewer: React.FC<Props> = ({representation, isRulesEnabled, rulesMatched, elapsedTime, color}) => {
return <AutoRepresentation representation={representation} isRulesEnabled={isRulesEnabled} rulesMatched={rulesMatched} elapsedTime={elapsedTime} color={color}/>
};
export default EntryViewer;

View File

@@ -36,8 +36,8 @@
border-left: 5px $failure-color solid
.ruleNumberText
font-size: 12px;
font-style: italic;
font-size: 12px
font-weight: 600
.ruleNumberTextFailure
color: #DB2156