[TRA-3659] Fix rules (#271)

* Fix rules

* Not reay, error on running

* Empty dissector Rules()

* almost working

* Finally, fixed

* undo changes on agent/pkg/utils/har.go

* fix not showing service on rules detail

* Update tap/api/api.go

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>

* Update agent/pkg/controllers/entries_controller.go

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>

* Update agent/pkg/controllers/entries_controller.go

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>

* unwrap Data

* Fix bug off using more than one latency rule that always get the first.

* fix json type, decoding base64 before unmarshal

* Run `go mod tidy` on `cli/go.sum`

* Fix the linting issues

* Remove a `FIXME` comment

* Remove a CSS rule

* Adapt `ruleNumberText` CSS class to the design language of the UI

* Fix an issue in the UI related to `rule.Latency` slipping out

* Removed unecessary codes.

Co-authored-by: gadotroee <55343099+gadotroee@users.noreply.github.com>
Co-authored-by: M. Mert Yildiran <mehmet@up9.com>
This commit is contained in:
Selton Fiuza 2021-09-18 14:02:18 -03:00 committed by GitHub
parent b9d2e671c7
commit 6337b75f0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 112 additions and 97 deletions

View File

@ -116,6 +116,15 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
baseEntry := extension.Dissector.Summarize(mizuEntry) baseEntry := extension.Dissector.Summarize(mizuEntry)
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry) mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
database.CreateEntry(mizuEntry) database.CreateEntry(mizuEntry)
if extension.Protocol.Name == "http" {
var pair tapApi.RequestResponsePair
json.Unmarshal([]byte(mizuEntry.Entry), &pair)
harEntry, _ := utils.NewEntry(&pair)
rules, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
baseEntry.Rules = rules
baseEntry.Latency = mizuEntry.ElapsedTime
}
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(baseEntry) baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(baseEntry)
BroadcastToBrowserClients(baseEntryBytes) BroadcastToBrowserClients(baseEntryBytes)
} }

View File

@ -3,7 +3,6 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/google/martian/har"
"mizuserver/pkg/database" "mizuserver/pkg/database"
"mizuserver/pkg/models" "mizuserver/pkg/models"
"mizuserver/pkg/providers" "mizuserver/pkg/providers"
@ -13,6 +12,8 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/google/martian/har"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/romana/rlog" "github.com/romana/rlog"
@ -140,11 +141,23 @@ func GetEntry(c *gin.Context) {
extension := extensionsMap[entryData.ProtocolName] extension := extensionsMap[entryData.ProtocolName]
protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData) protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData)
var rules []map[string]interface{}
if entryData.ProtocolName == "http" {
var pair tapApi.RequestResponsePair
json.Unmarshal([]byte(entryData.Entry), &pair)
harEntry, _ := utils.NewEntry(&pair)
_, rulesMatched := models.RunValidationRulesState(*harEntry, entryData.Service)
inrec, _ := json.Marshal(rulesMatched)
json.Unmarshal(inrec, &rules)
}
c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{ c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{
Protocol: protocol, Protocol: protocol,
Representation: string(representation), Representation: string(representation),
BodySize: bodySize, BodySize: bodySize,
Data: entryData, Data: entryData,
Rules: rules,
}) })
} }

View File

@ -2,9 +2,10 @@ package models
import ( import (
"encoding/json" "encoding/json"
tapApi "github.com/up9inc/mizu/tap/api" tapApi "github.com/up9inc/mizu/tap/api"
"mizuserver/pkg/rules" "mizuserver/pkg/rules"
"mizuserver/pkg/utils"
"github.com/google/martian/har" "github.com/google/martian/har"
"github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared"
@ -15,15 +16,6 @@ func GetEntry(r *tapApi.MizuEntry, v tapApi.DataUnmarshaler) error {
return v.UnmarshalData(r) return v.UnmarshalData(r)
} }
// TODO: until we fixed the Rules feature
//func NewApplicableRules(status bool, latency int64, number int) tapApi.ApplicableRules {
// ar := tapApi.ApplicableRules{}
// ar.Status = status
// ar.Latency = latency
// ar.NumberOfRules = number
// return ar
//}
type EntriesFilter struct { type EntriesFilter struct {
Limit int `form:"limit" validate:"required,min=1,max=200"` Limit int `form:"limit" validate:"required,min=1,max=200"`
Operator string `form:"operator" validate:"required,oneof='lt' 'gt'"` Operator string `form:"operator" validate:"required,oneof='lt' 'gt'"`
@ -105,33 +97,8 @@ type ExtendedCreator struct {
Source *string `json:"_source"` Source *string `json:"_source"`
} }
type FullEntryWithPolicy struct { func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched) {
RulesMatched []rules.RulesMatched `json:"rulesMatched,omitempty"` resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
Entry har.Entry `json:"entry"` statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
Service string `json:"service"` return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend
} }
func (fewp *FullEntryWithPolicy) UnmarshalData(entry *tapApi.MizuEntry) error {
var pair tapApi.RequestResponsePair
if err := json.Unmarshal([]byte(entry.Entry), &pair); err != nil {
return err
}
harEntry, err := utils.NewEntry(&pair)
if err != nil {
return err
}
fewp.Entry = *harEntry
_, resultPolicyToSend := rules.MatchRequestPolicy(fewp.Entry, entry.Service)
fewp.RulesMatched = resultPolicyToSend
fewp.Service = entry.Service
return nil
}
// TODO: until we fixed the Rules feature
//func RunValidationRulesState(harEntry har.Entry, service string) tapApi.ApplicableRules {
// numberOfRules, resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
// statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
// ar := NewApplicableRules(statusPolicyToSend, latency, numberOfRules)
// return ar
//}

View File

@ -1,6 +1,7 @@
package rules package rules
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
@ -41,7 +42,7 @@ func ValidateService(serviceFromRule string, service string) bool {
return true return true
} }
func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched) { func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched {
enforcePolicy, _ := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName)) enforcePolicy, _ := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
var resultPolicyToSend []RulesMatched var resultPolicyToSend []RulesMatched
for _, rule := range enforcePolicy.Rules { for _, rule := range enforcePolicy.Rules {
@ -50,7 +51,8 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
} }
if rule.Type == "json" { if rule.Type == "json" {
var bodyJsonMap interface{} var bodyJsonMap interface{}
if err := json.Unmarshal(harEntry.Response.Content.Text, &bodyJsonMap); err != nil { contentTextDecoded, _ := base64.StdEncoding.DecodeString(string(harEntry.Response.Content.Text))
if err := json.Unmarshal(contentTextDecoded, &bodyJsonMap); err != nil {
continue continue
} }
out, err := jsonpath.Read(bodyJsonMap, rule.Key) out, err := jsonpath.Read(bodyJsonMap, rule.Key)
@ -63,6 +65,7 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
if err != nil { if err != nil {
continue continue
} }
fmt.Println(matchValue, rule.Value)
} else { } else {
val := fmt.Sprint(out) val := fmt.Sprint(out)
matchValue, err = regexp.MatchString(rule.Value, val) matchValue, err = regexp.MatchString(rule.Value, val)
@ -89,22 +92,28 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule) resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
} }
} }
return len(enforcePolicy.Rules), resultPolicyToSend return resultPolicyToSend
} }
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64, int) { func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {
if len(rulesMatched) == 0 { var numberOfRulesMatched = len(rulesMatched)
return false, 0, 0 var latency int64 = -1
if numberOfRulesMatched == 0 {
return false, 0, numberOfRulesMatched
} }
for _, rule := range rulesMatched { for _, rule := range rulesMatched {
if rule.Matched == false { if rule.Matched == false {
return false, -1, len(rulesMatched) return false, latency, numberOfRulesMatched
} else {
if strings.ToLower(rule.Rule.Type) == "latency" {
if rule.Rule.Latency < latency || latency == -1 {
latency = rule.Rule.Latency
}
}
} }
} }
for _, rule := range rulesMatched {
if strings.ToLower(rule.Rule.Type) == "latency" { return true, latency, numberOfRulesMatched
return true, rule.Rule.Latency, len(rulesMatched)
}
}
return true, -1, len(rulesMatched)
} }

View File

@ -99,6 +99,11 @@ type RulePolicy struct {
Name string `yaml:"name"` Name string `yaml:"name"`
} }
type RulesMatched struct {
Matched bool `json:"matched"`
Rule RulePolicy `json:"rule"`
}
func (r *RulePolicy) validateType() bool { func (r *RulePolicy) validateType() bool {
permitedTypes := []string{"json", "header", "latency"} permitedTypes := []string{"json", "header", "latency"}
_, found := Find(permitedTypes, r.Type) _, found := Find(permitedTypes, r.Type)

View File

@ -133,10 +133,11 @@ type MizuEntry struct {
} }
type MizuEntryWrapper struct { type MizuEntryWrapper struct {
Protocol Protocol `json:"protocol"` Protocol Protocol `json:"protocol"`
Representation string `json:"representation"` Representation string `json:"representation"`
BodySize int64 `json:"bodySize"` BodySize int64 `json:"bodySize"`
Data MizuEntry `json:"data"` Data MizuEntry `json:"data"`
Rules []map[string]interface{} `json:"rulesMatched,omitempty"`
} }
type BaseEntryDetails struct { type BaseEntryDetails struct {

View File

@ -42,7 +42,6 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime}) => {
<div style={{right: "30px", position: "absolute", display: "flex"}}> <div style={{right: "30px", position: "absolute", display: "flex"}}>
{response.payload && <div style={{margin: "0 18px", opacity: 0.5}}>{formatSize(bodySize)}</div>} {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> <div style={{marginRight: 18, opacity: 0.5}}>{Math.round(elapsedTime)}ms</div>
<div style={{opacity: 0.5}}>{'rulesMatched' in data ? data.rulesMatched?.length : '0'} Rules Applied</div>
</div> </div>
</div>; </div>;
}; };
@ -72,7 +71,7 @@ export const EntryDetailed: React.FC<EntryDetailedProps> = ({entryData}) => {
/> />
{entryData.data && <EntrySummary data={entryData.data}/>} {entryData.data && <EntrySummary data={entryData.data}/>}
<> <>
{entryData.data && <EntryViewer representation={entryData.representation} color={entryData.protocol.backgroundColor}/>} {entryData.data && <EntryViewer representation={entryData.representation} rulesMatched={entryData.rulesMatched} elapsedTime={entryData.data.elapsedTime} color={entryData.protocol.backgroundColor}/>}
</> </>
</> </>
}; };

View File

@ -215,10 +215,10 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({serv
<> <>
{ {
rule.Key && rule.Key &&
<tr className={styles.dataValue}><td><b>Key</b>:</td><td>{rule.Key}</td></tr> <tr className={styles.dataValue}><td><b>Key:</b></td> <td>{rule.Key}</td></tr>
} }
{ {
rule.Latency && rule.Latency !== 0 &&
<tr className={styles.dataValue}><td><b>Latency:</b></td> <td>{rule.Latency}</td></tr> <tr className={styles.dataValue}><td><b>Latency:</b></td> <td>{rule.Latency}</td></tr>
} }
{ {
@ -231,7 +231,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({serv
} }
{ {
rule.Service && rule.Service &&
<tr className={styles.dataValue}><td><b>Service:</b></td> <td>{service}</td></tr> <tr className={styles.dataValue}><td><b>Service:</b></td> <td>{rule.Service}</td></tr>
} }
{ {
rule.Type && rule.Type &&
@ -251,7 +251,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({serv
</tbody> </tbody>
</table> </table>
</EntrySectionContainer> </EntrySectionContainer>
</> : <span/> </> : <span className={styles.noRules}>No rules could be applied to this request.</span>
} }
</React.Fragment> </React.Fragment>
} }

View File

@ -33,8 +33,7 @@ const SectionsRepresentation: React.FC<any> = ({data, color}) => {
return <>{sections}</>; return <>{sections}</>;
} }
const AutoRepresentation: React.FC<any> = ({representation, color}) => { const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapsedTime, color}) => {
const rulesMatched = []
const TABS = [ const TABS = [
{ {
tab: 'request' tab: 'request'
@ -68,8 +67,7 @@ const AutoRepresentation: React.FC<any> = ({representation, color}) => {
<SectionsRepresentation data={response} color={color}/> <SectionsRepresentation data={response} color={color}/>
</React.Fragment>} </React.Fragment>}
{currentTab === TABS[2].tab && <React.Fragment> {currentTab === TABS[2].tab && <React.Fragment>
{// FIXME: Fix here <EntryTablePolicySection service={representation.service} title={'Rule'} color={color} latency={elapsedTime} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>
<EntryTablePolicySection service={representation.service} title={'Rule'} color={color} latency={0} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>}
</React.Fragment>} </React.Fragment>}
</div>} </div>}
</div>; </div>;
@ -77,11 +75,13 @@ const AutoRepresentation: React.FC<any> = ({representation, color}) => {
interface Props { interface Props {
representation: any; representation: any;
color: string, rulesMatched: any;
color: string;
elapsedTime: number;
} }
const EntryViewer: React.FC<Props> = ({representation, color}) => { const EntryViewer: React.FC<Props> = ({representation, rulesMatched, elapsedTime, color}) => {
return <AutoRepresentation representation={representation} color={color}/> return <AutoRepresentation representation={representation} rulesMatched={rulesMatched} elapsedTime={elapsedTime} color={color}/>
}; };
export default EntryViewer; export default EntryViewer;

View File

@ -19,20 +19,31 @@
.rowSelected .rowSelected
border: 1px $blue-color solid border: 1px $blue-color solid
// border-left: 5px $blue-color solid
margin-left: 10px
margin-right: 3px margin-right: 3px
.ruleSuccessRow .ruleSuccessRow
border: 1px $success-color solid background: #E8FFF1
// border-left: 5px $success-color solid
.ruleSuccessRowSelected
border: 1px #6FCF97 solid
border-left: 5px #6FCF97 solid
.ruleFailureRow .ruleFailureRow
background: #FFE9EF background: #FFE9EF
.ruleFailureRowSelected .ruleFailureRowSelected
border: 1px $failure-color solid border: 1px $failure-color solid
// border-left: 5px $failure-color solid border-left: 5px $failure-color solid
.ruleNumberText
font-size: 12px;
font-style: italic;
.ruleNumberTextFailure
color: #DB2156
.ruleNumberTextSuccess
color: #219653
.service .service
text-overflow: ellipsis text-overflow: ellipsis

View File

@ -43,6 +43,7 @@ interface EntryProps {
export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSelected, style}) => { export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSelected, style}) => {
const classification = getClassification(entry.statusCode) const classification = getClassification(entry.statusCode)
const numberOfRules = entry.rules.numberOfRules
let ingoingIcon; let ingoingIcon;
let outgoingIcon; let outgoingIcon;
switch(classification) { switch(classification) {
@ -62,47 +63,39 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
break; break;
} }
} }
// let additionalRulesProperties = ""; let additionalRulesProperties = "";
// let ruleSuccess: boolean; let ruleSuccess: boolean;
let rule = 'latency' in entry.rules let rule = 'latency' in entry.rules
if (rule) { if (rule) {
if (entry.rules.latency !== -1) { if (entry.rules.latency !== -1) {
if (entry.rules.latency >= entry.latency) { if (entry.rules.latency >= entry.latency) {
// additionalRulesProperties = styles.ruleSuccessRow additionalRulesProperties = styles.ruleSuccessRow
// ruleSuccess = true ruleSuccess = true
} else { } else {
// additionalRulesProperties = styles.ruleFailureRow additionalRulesProperties = styles.ruleFailureRow
// ruleSuccess = false ruleSuccess = false
} }
if (isSelected) { if (isSelected) {
// additionalRulesProperties += ` ${entry.rules.latency >= entry.latency ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}` additionalRulesProperties += ` ${entry.rules.latency >= entry.latency ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
} }
} else { } else {
if (entry.rules.status) { if (entry.rules.status) {
// additionalRulesProperties = styles.ruleSuccessRow additionalRulesProperties = styles.ruleSuccessRow
// ruleSuccess = true ruleSuccess = true
} else { } else {
// additionalRulesProperties = styles.ruleFailureRow additionalRulesProperties = styles.ruleFailureRow
// ruleSuccess = false ruleSuccess = false
} }
if (isSelected) { if (isSelected) {
// additionalRulesProperties += ` ${entry.rules.status ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}` additionalRulesProperties += ` ${entry.rules.status ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
} }
} }
} }
let backgroundColor = "";
if ('latency' in entry.rules) {
if (entry.rules.latency !== -1) {
backgroundColor = entry.rules.latency >= entry.latency ? styles.ruleSuccessRow : styles.ruleFailureRow
} else {
backgroundColor = entry.rules.status ? styles.ruleSuccessRow : styles.ruleFailureRow
}
}
return <> return <>
<div <div
id={entry.id} id={entry.id}
className={`${styles.row} className={`${styles.row}
${isSelected ? styles.rowSelected : backgroundColor}`} ${isSelected && !rule ? styles.rowSelected : additionalRulesProperties}`}
onClick={() => setFocusedEntryId(entry.id)} onClick={() => setFocusedEntryId(entry.id)}
style={{ style={{
border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid", border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid",
@ -122,6 +115,13 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
<span title="Service Name">{entry.service}</span> <span title="Service Name">{entry.service}</span>
</div> </div>
</div> </div>
{
rule ?
<div className={`${styles.ruleNumberText} ${ruleSuccess ? styles.ruleNumberTextSuccess : styles.ruleNumberTextFailure}`}>
{`Rules (${numberOfRules})`}
</div>
: ""
}
<div className={styles.directionContainer}> <div className={styles.directionContainer}>
<span className={styles.port} title="Source Port">{entry.sourcePort}</span> <span className={styles.port} title="Source Port">{entry.sourcePort}</span>
{entry.isOutgoing ? {entry.isOutgoing ?
@ -138,4 +138,5 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
</div> </div>
</div> </div>
</> </>
};
}