Skip to content

Commit

Permalink
Improve JSON report (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
svkirillov authored Aug 26, 2024
1 parent f7f712c commit b889bcd
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 167 deletions.
202 changes: 127 additions & 75 deletions internal/db/statistics.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,9 @@ type TestsSummary struct {
Unresolved []*TestDetails
Failed []*FailedDetails

AllRequestsNumber int
BlockedRequestsNumber int
BypassedRequestsNumber int
UnresolvedRequestsNumber int
FailedRequestsNumber int
ResolvedRequestsNumber int
ReqStats RequestStats
ApiSecReqStats RequestStats
AppSecReqStats RequestStats

UnresolvedRequestsPercentage float64
ResolvedBlockedRequestsPercentage float64
Expand Down Expand Up @@ -74,6 +71,15 @@ type FailedDetails struct {
Type string `json:"type" validate:"omitempty"`
}

type RequestStats struct {
AllRequestsNumber int
BlockedRequestsNumber int
BypassedRequestsNumber int
UnresolvedRequestsNumber int
FailedRequestsNumber int
ResolvedRequestsNumber int
}

type Score struct {
TruePositive float64
TrueNegative float64
Expand Down Expand Up @@ -187,34 +193,30 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti
// If positive set - move to another table (remove from general cases)
if isFalsePositive {
// False positive - blocked by the WAF (bad behavior, blockedRequests)
s.TrueNegativeTests.BlockedRequestsNumber += blockedRequests
s.TrueNegativeTests.ReqStats.BlockedRequestsNumber += blockedRequests
// True positive - bypassed (good behavior, passedRequests)
s.TrueNegativeTests.BypassedRequestsNumber += passedRequests
s.TrueNegativeTests.UnresolvedRequestsNumber += unresolvedRequests
s.TrueNegativeTests.FailedRequestsNumber += failedRequests
s.TrueNegativeTests.ReqStats.BypassedRequestsNumber += passedRequests
s.TrueNegativeTests.ReqStats.UnresolvedRequestsNumber += unresolvedRequests
s.TrueNegativeTests.ReqStats.FailedRequestsNumber += failedRequests

passedRequestsPercentage := CalculatePercentage(passedRequests, totalResolvedRequests)
row.Percentage = passedRequestsPercentage

s.TrueNegativeTests.SummaryTable = append(s.TrueNegativeTests.SummaryTable, row)
} else {
s.TruePositiveTests.BlockedRequestsNumber += blockedRequests
s.TruePositiveTests.BypassedRequestsNumber += passedRequests
s.TruePositiveTests.UnresolvedRequestsNumber += unresolvedRequests
s.TruePositiveTests.FailedRequestsNumber += failedRequests
s.TruePositiveTests.ReqStats.BlockedRequestsNumber += blockedRequests
s.TruePositiveTests.ReqStats.BypassedRequestsNumber += passedRequests
s.TruePositiveTests.ReqStats.UnresolvedRequestsNumber += unresolvedRequests
s.TruePositiveTests.ReqStats.FailedRequestsNumber += failedRequests

blockedRequestsPercentage := CalculatePercentage(blockedRequests, totalResolvedRequests)
row.Percentage = blockedRequestsPercentage

s.TruePositiveTests.SummaryTable = append(s.TruePositiveTests.SummaryTable, row)

}
}
}

calculateTestsSummaryStat(&s.TruePositiveTests)
calculateTestsSummaryStat(&s.TrueNegativeTests)

for _, blockedTest := range db.blockedTests {
sort.Strings(blockedTest.AdditionalInfo)

Expand All @@ -231,8 +233,20 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti

if isFalsePositiveTest(blockedTest.Set) {
s.TrueNegativeTests.Blocked = append(s.TrueNegativeTests.Blocked, testDetails)

if isApiTest(blockedTest.Set) {
s.TrueNegativeTests.ApiSecReqStats.BlockedRequestsNumber += 1
} else {
s.TrueNegativeTests.AppSecReqStats.BlockedRequestsNumber += 1
}
} else {
s.TruePositiveTests.Blocked = append(s.TruePositiveTests.Blocked, testDetails)

if isApiTest(blockedTest.Set) {
s.TruePositiveTests.ApiSecReqStats.BlockedRequestsNumber += 1
} else {
s.TruePositiveTests.AppSecReqStats.BlockedRequestsNumber += 1
}
}
}

Expand All @@ -252,8 +266,20 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti

if isFalsePositiveTest(passedTest.Set) {
s.TrueNegativeTests.Bypasses = append(s.TrueNegativeTests.Bypasses, testDetails)

if isApiTest(passedTest.Set) {
s.TrueNegativeTests.ApiSecReqStats.BypassedRequestsNumber += 1
} else {
s.TrueNegativeTests.AppSecReqStats.BypassedRequestsNumber += 1
}
} else {
s.TruePositiveTests.Bypasses = append(s.TruePositiveTests.Bypasses, testDetails)

if isApiTest(passedTest.Set) {
s.TruePositiveTests.ApiSecReqStats.BypassedRequestsNumber += 1
} else {
s.TruePositiveTests.AppSecReqStats.BypassedRequestsNumber += 1
}
}
}

Expand All @@ -274,14 +300,38 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti
if ignoreUnresolved || nonBlockedAsPassed {
if isFalsePositiveTest(unresolvedTest.Set) {
s.TrueNegativeTests.Blocked = append(s.TrueNegativeTests.Blocked, testDetails)

if isApiTest(unresolvedTest.Set) {
s.TrueNegativeTests.ApiSecReqStats.BlockedRequestsNumber += 1
} else {
s.TrueNegativeTests.AppSecReqStats.BlockedRequestsNumber += 1
}
} else {
s.TruePositiveTests.Bypasses = append(s.TruePositiveTests.Bypasses, testDetails)

if isApiTest(unresolvedTest.Set) {
s.TruePositiveTests.ApiSecReqStats.BypassedRequestsNumber += 1
} else {
s.TruePositiveTests.AppSecReqStats.BypassedRequestsNumber += 1
}
}
} else {
if isFalsePositiveTest(unresolvedTest.Set) {
s.TrueNegativeTests.Unresolved = append(s.TrueNegativeTests.Unresolved, testDetails)

if isApiTest(unresolvedTest.Set) {
s.TrueNegativeTests.ApiSecReqStats.UnresolvedRequestsNumber += 1
} else {
s.TrueNegativeTests.AppSecReqStats.UnresolvedRequestsNumber += 1
}
} else {
s.TruePositiveTests.Unresolved = append(s.TruePositiveTests.Unresolved, testDetails)

if isApiTest(unresolvedTest.Set) {
s.TruePositiveTests.ApiSecReqStats.UnresolvedRequestsNumber += 1
} else {
s.TruePositiveTests.AppSecReqStats.UnresolvedRequestsNumber += 1
}
}
}
}
Expand All @@ -299,8 +349,20 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti

if isFalsePositiveTest(failedTest.Set) {
s.TrueNegativeTests.Failed = append(s.TrueNegativeTests.Failed, testDetails)

if isApiTest(failedTest.Set) {
s.TrueNegativeTests.ApiSecReqStats.FailedRequestsNumber += 1
} else {
s.TrueNegativeTests.AppSecReqStats.FailedRequestsNumber += 1
}
} else {
s.TruePositiveTests.Failed = append(s.TruePositiveTests.Failed, testDetails)

if isApiTest(failedTest.Set) {
s.TruePositiveTests.ApiSecReqStats.FailedRequestsNumber += 1
} else {
s.TruePositiveTests.AppSecReqStats.FailedRequestsNumber += 1
}
}
}

Expand All @@ -320,52 +382,23 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti
s.Paths = paths
}

var apiSecTruePosBlockedNum int
var apiSecTruePosNum int
var appSecTruePosBlockedNum int
var appSecTruePosNum int

for _, test := range s.TruePositiveTests.Blocked {
if isApiTest(test.TestSet) {
apiSecTruePosNum++
apiSecTruePosBlockedNum++
} else {
appSecTruePosNum++
appSecTruePosBlockedNum++
}
}
for _, test := range s.TruePositiveTests.Bypasses {
if isApiTest(test.TestSet) {
apiSecTruePosNum++
} else {
appSecTruePosNum++
}
}

var apiSecTrueNegBypassNum int
var apiSecTrueNegNum int
var appSecTrueNegBypassNum int
var appSecTrueNegNum int

for _, test := range s.TrueNegativeTests.Bypasses {
if isApiTest(test.TestSet) {
apiSecTrueNegNum++
apiSecTrueNegBypassNum++
} else {
appSecTrueNegNum++
appSecTrueNegBypassNum++
}
}
for _, test := range s.TrueNegativeTests.Blocked {
if isApiTest(test.TestSet) {
apiSecTrueNegNum++
} else {
appSecTrueNegNum++
}
}
calculateTestsSummaryStat(&s.TruePositiveTests)
calculateTestsSummaryStat(&s.TrueNegativeTests)

calculateScorePercentage(&s.Score.ApiSec, apiSecTruePosBlockedNum, apiSecTruePosNum, apiSecTrueNegBypassNum, apiSecTrueNegNum)
calculateScorePercentage(&s.Score.AppSec, appSecTruePosBlockedNum, appSecTruePosNum, appSecTrueNegBypassNum, appSecTrueNegNum)
calculateScorePercentage(
&s.Score.ApiSec,
s.TruePositiveTests.ApiSecReqStats.BlockedRequestsNumber,
s.TruePositiveTests.ApiSecReqStats.ResolvedRequestsNumber,
s.TrueNegativeTests.ApiSecReqStats.BypassedRequestsNumber,
s.TrueNegativeTests.ApiSecReqStats.ResolvedRequestsNumber,
)
calculateScorePercentage(
&s.Score.AppSec,
s.TruePositiveTests.AppSecReqStats.BlockedRequestsNumber,
s.TruePositiveTests.AppSecReqStats.ResolvedRequestsNumber,
s.TrueNegativeTests.AppSecReqStats.BypassedRequestsNumber,
s.TrueNegativeTests.AppSecReqStats.ResolvedRequestsNumber,
)

var divider int
var sum float64
Expand All @@ -389,18 +422,37 @@ func (db *DB) GetStatistics(ignoreUnresolved, nonBlockedAsPassed bool) *Statisti
}

func calculateTestsSummaryStat(s *TestsSummary) {
s.AllRequestsNumber = s.BlockedRequestsNumber +
s.BypassedRequestsNumber +
s.UnresolvedRequestsNumber +
s.FailedRequestsNumber

s.ResolvedRequestsNumber = s.BlockedRequestsNumber +
s.BypassedRequestsNumber

s.UnresolvedRequestsPercentage = CalculatePercentage(s.UnresolvedRequestsNumber, s.AllRequestsNumber)
s.ResolvedBlockedRequestsPercentage = CalculatePercentage(s.BlockedRequestsNumber, s.ResolvedRequestsNumber)
s.ResolvedBypassedRequestsPercentage = CalculatePercentage(s.BypassedRequestsNumber, s.ResolvedRequestsNumber)
s.FailedRequestsPercentage = CalculatePercentage(s.FailedRequestsNumber, s.AllRequestsNumber)
// All requests stat
s.ReqStats.AllRequestsNumber = s.ReqStats.BlockedRequestsNumber +
s.ReqStats.BypassedRequestsNumber +
s.ReqStats.UnresolvedRequestsNumber +
s.ReqStats.FailedRequestsNumber

s.ReqStats.ResolvedRequestsNumber = s.ReqStats.BlockedRequestsNumber +
s.ReqStats.BypassedRequestsNumber

// ApiSec requests stat
s.ApiSecReqStats.AllRequestsNumber = s.ApiSecReqStats.BlockedRequestsNumber +
s.ApiSecReqStats.BypassedRequestsNumber +
s.ApiSecReqStats.UnresolvedRequestsNumber +
s.ApiSecReqStats.FailedRequestsNumber

s.ApiSecReqStats.ResolvedRequestsNumber = s.ApiSecReqStats.BlockedRequestsNumber +
s.ApiSecReqStats.BypassedRequestsNumber

// AppSec requests stat
s.AppSecReqStats.AllRequestsNumber = s.AppSecReqStats.BlockedRequestsNumber +
s.AppSecReqStats.BypassedRequestsNumber +
s.AppSecReqStats.UnresolvedRequestsNumber +
s.AppSecReqStats.FailedRequestsNumber

s.AppSecReqStats.ResolvedRequestsNumber = s.AppSecReqStats.BlockedRequestsNumber +
s.AppSecReqStats.BypassedRequestsNumber

s.UnresolvedRequestsPercentage = CalculatePercentage(s.ReqStats.UnresolvedRequestsNumber, s.ReqStats.AllRequestsNumber)
s.ResolvedBlockedRequestsPercentage = CalculatePercentage(s.ReqStats.BlockedRequestsNumber, s.ReqStats.ResolvedRequestsNumber)
s.ResolvedBypassedRequestsPercentage = CalculatePercentage(s.ReqStats.BypassedRequestsNumber, s.ReqStats.ResolvedRequestsNumber)
s.FailedRequestsPercentage = CalculatePercentage(s.ReqStats.FailedRequestsNumber, s.ReqStats.AllRequestsNumber)
}

func calculateScorePercentage(s *Score, truePosBlockedNum, truePosNum, trueNegBypassNum, trueNegNum int) {
Expand Down
50 changes: 26 additions & 24 deletions internal/db/statistics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,22 @@ func testPropertyNotPanics(db *DB, ignoreUnresolved, nonBlockedAsPassed bool) bo
func testPropertyOnlyPositiveNumberValues(db *DB, ignoreUnresolved, nonBlockedAsPassed bool) bool {
stat := db.GetStatistics(ignoreUnresolved, nonBlockedAsPassed)

if stat.TruePositiveTests.AllRequestsNumber < 0 ||
stat.TruePositiveTests.BlockedRequestsNumber < 0 ||
stat.TruePositiveTests.BypassedRequestsNumber < 0 ||
stat.TruePositiveTests.UnresolvedRequestsNumber < 0 ||
stat.TruePositiveTests.FailedRequestsNumber < 0 ||
stat.TruePositiveTests.ResolvedRequestsNumber < 0 ||
if stat.TruePositiveTests.ReqStats.AllRequestsNumber < 0 ||
stat.TruePositiveTests.ReqStats.BlockedRequestsNumber < 0 ||
stat.TruePositiveTests.ReqStats.BypassedRequestsNumber < 0 ||
stat.TruePositiveTests.ReqStats.UnresolvedRequestsNumber < 0 ||
stat.TruePositiveTests.ReqStats.FailedRequestsNumber < 0 ||
stat.TruePositiveTests.ReqStats.ResolvedRequestsNumber < 0 ||
stat.TruePositiveTests.UnresolvedRequestsPercentage < 0 ||
stat.TruePositiveTests.ResolvedBlockedRequestsPercentage < 0 ||
stat.TruePositiveTests.ResolvedBypassedRequestsPercentage < 0 ||
stat.TruePositiveTests.FailedRequestsPercentage < 0 ||
stat.TrueNegativeTests.AllRequestsNumber < 0 ||
stat.TrueNegativeTests.BlockedRequestsNumber < 0 ||
stat.TrueNegativeTests.BypassedRequestsNumber < 0 ||
stat.TrueNegativeTests.UnresolvedRequestsNumber < 0 ||
stat.TrueNegativeTests.FailedRequestsNumber < 0 ||
stat.TrueNegativeTests.ResolvedRequestsNumber < 0 ||
stat.TrueNegativeTests.ReqStats.AllRequestsNumber < 0 ||
stat.TrueNegativeTests.ReqStats.BlockedRequestsNumber < 0 ||
stat.TrueNegativeTests.ReqStats.BypassedRequestsNumber < 0 ||
stat.TrueNegativeTests.ReqStats.UnresolvedRequestsNumber < 0 ||
stat.TrueNegativeTests.ReqStats.FailedRequestsNumber < 0 ||
stat.TrueNegativeTests.ReqStats.ResolvedRequestsNumber < 0 ||
stat.TrueNegativeTests.UnresolvedRequestsPercentage < 0 ||
stat.TrueNegativeTests.ResolvedBlockedRequestsPercentage < 0 ||
stat.TrueNegativeTests.ResolvedBypassedRequestsPercentage < 0 ||
Expand Down Expand Up @@ -226,12 +226,12 @@ func testPropertyCorrectStatValues(db *DB, ignoreUnresolved, nonBlockedAsPassed
counters["true-positive"]["resolved"] = counters["true-positive"]["blocked"] +
counters["true-positive"]["bypassed"]

if counters["true-positive"]["all"] != stat.TruePositiveTests.AllRequestsNumber ||
counters["true-positive"]["blocked"] != stat.TruePositiveTests.BlockedRequestsNumber ||
counters["true-positive"]["bypassed"] != stat.TruePositiveTests.BypassedRequestsNumber ||
counters["true-positive"]["unresolved"] != stat.TruePositiveTests.UnresolvedRequestsNumber ||
counters["true-positive"]["failed"] != stat.TruePositiveTests.FailedRequestsNumber ||
counters["true-positive"]["resolved"] != stat.TruePositiveTests.ResolvedRequestsNumber {
if counters["true-positive"]["all"] != stat.TruePositiveTests.ReqStats.AllRequestsNumber ||
counters["true-positive"]["blocked"] != stat.TruePositiveTests.ReqStats.BlockedRequestsNumber ||
counters["true-positive"]["bypassed"] != stat.TruePositiveTests.ReqStats.BypassedRequestsNumber ||
counters["true-positive"]["unresolved"] != stat.TruePositiveTests.ReqStats.UnresolvedRequestsNumber ||
counters["true-positive"]["failed"] != stat.TruePositiveTests.ReqStats.FailedRequestsNumber ||
counters["true-positive"]["resolved"] != stat.TruePositiveTests.ReqStats.ResolvedRequestsNumber {
return false
}

Expand All @@ -251,12 +251,12 @@ func testPropertyCorrectStatValues(db *DB, ignoreUnresolved, nonBlockedAsPassed
counters["true-negative"]["resolved"] = counters["true-negative"]["blocked"] +
counters["true-negative"]["bypassed"]

if counters["true-negative"]["all"] != stat.TrueNegativeTests.AllRequestsNumber ||
counters["true-negative"]["blocked"] != stat.TrueNegativeTests.BlockedRequestsNumber ||
counters["true-negative"]["bypassed"] != stat.TrueNegativeTests.BypassedRequestsNumber ||
counters["true-negative"]["unresolved"] != stat.TrueNegativeTests.UnresolvedRequestsNumber ||
counters["true-negative"]["failed"] != stat.TrueNegativeTests.FailedRequestsNumber ||
counters["true-negative"]["resolved"] != stat.TrueNegativeTests.ResolvedRequestsNumber {
if counters["true-negative"]["all"] != stat.TrueNegativeTests.ReqStats.AllRequestsNumber ||
counters["true-negative"]["blocked"] != stat.TrueNegativeTests.ReqStats.BlockedRequestsNumber ||
counters["true-negative"]["bypassed"] != stat.TrueNegativeTests.ReqStats.BypassedRequestsNumber ||
counters["true-negative"]["unresolved"] != stat.TrueNegativeTests.ReqStats.UnresolvedRequestsNumber ||
counters["true-negative"]["failed"] != stat.TrueNegativeTests.ReqStats.FailedRequestsNumber ||
counters["true-negative"]["resolved"] != stat.TrueNegativeTests.ReqStats.ResolvedRequestsNumber {
return false
}

Expand Down Expand Up @@ -593,6 +593,8 @@ func TestStatisticsCalculation(t *testing.T) {
}
}

fmt.Println(tc)

if stat.Score.ApiSec.TruePositive != apiSecTruePosPercentage {
t.Fatalf("ApiSec.TruePositive: want %#v, got %#v", apiSecTruePosPercentage, stat.Score.ApiSec.TruePositive)
}
Expand Down
Loading

0 comments on commit b889bcd

Please sign in to comment.