Skip to content
This repository has been archived by the owner on Sep 15, 2021. It is now read-only.

Commit

Permalink
Merge pull request #39 from hmrc/PLATUI-111
Browse files Browse the repository at this point in the history
PLATUI-111: Check if test URL is proxied via ZAP and fail when not proxied
  • Loading branch information
dabd authored Jul 30, 2019
2 parents 4588872 + 04d2c98 commit ecd0a16
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 43 deletions.
2 changes: 2 additions & 0 deletions src/main/scala/uk/gov/hmrc/zap/ZapException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ case class ZapException(s: String) extends Exception(s)

case class SpiderScanException(message: String) extends Exception(message)

case class PassiveScanException(message: String) extends Exception(message)

case class ActiveScanException(message: String) extends Exception(message)

case class ZapAlertException(message: String) extends Exception(message)
Expand Down
8 changes: 5 additions & 3 deletions src/main/scala/uk/gov/hmrc/zap/ZapReport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ import uk.gov.hmrc.zap.logger.ZapLogger._

object ZapReport {

def generateHtmlReport(relevantAlerts: List[ZapAlert], failureThreshold: String, spiderScanStatus: ScanStatus,
activeScanStatus: ScanStatus, missingScanners: List[Scanner], zapVersion: String): String = {
report.html.index(relevantAlerts, failureThreshold, spiderScanStatus, activeScanStatus, missingScanners, zapVersion).toString()
def generateHtmlReport(zapReport: ZapReport): String = {
report.html.index(zapReport).toString()
}

def writeToFile(report: String): Unit = {
Expand All @@ -40,3 +39,6 @@ object ZapReport {
log.info(s"HTML Report generated: file://${file.getAbsolutePath}")
}
}

case class ZapReport(relevantAlerts: List[ZapAlert], failureThreshold: String, passiveScanStatus: ScanStatus,spiderScanStatus: ScanStatus,
activeScanStatus: ScanStatus, missingScanners: List[Scanner], zapVersion: String)
10 changes: 8 additions & 2 deletions src/main/scala/uk/gov/hmrc/zap/ZapTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ trait ZapTest extends BeforeAndAfterAll with HealthCheck with ZapOrchestrator {
if (zapConfiguration.debugHealthCheck) {
healthCheck(zapConfiguration.testUrl)
}
if (zapScan.passiveScanStatus == ScanNotCompleted) {
throw PassiveScanException("Test URL did not proxy via ZAP (OR) Passive Scan did not complete within configured duration." +
"See ERROR message in the logs above.")
}
zapSetup.setConnectionTimeout()
zapSetup.checkMissingScanners
zapSetup.setUpPolicy
Expand All @@ -60,8 +64,10 @@ trait ZapTest extends BeforeAndAfterAll with HealthCheck with ZapOrchestrator {
private def createTestReport(): Unit = {
lazy val zapVersion = zapSetup.findZapVersion

writeToFile(generateHtmlReport(relevantAlerts.sortBy {_.severityScore()}, zapConfiguration.failureThreshold,
zapScan.spiderRunStatus, zapScan.activeScanStatus, zapSetup.checkMissingScanners, zapVersion))
val zapReport = ZapReport(relevantAlerts.sortBy {_.severityScore()}, zapConfiguration.failureThreshold, zapScan.passiveScanStatus,
zapScan.spiderRunStatus, zapScan.activeScanStatus, zapSetup.checkMissingScanners, zapVersion)

writeToFile(generateHtmlReport(zapReport))
}
}

Expand Down
32 changes: 32 additions & 0 deletions src/main/scala/uk/gov/hmrc/zap/api/ZapScan.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ class ZapScan(zapClient: ZapClient) extends Eventually {
lazy val activeScanStatus: ScanStatus = scanStatus("/json/ascan/view/status")
lazy val spiderRunStatus: ScanStatus = spiderStatus

lazy val passiveScanStatus: ScanStatus = {
if (isUrlProxiedViaZap) {
recordsToScanStatus match {
case ScanCompleted => ScanCompleted
case ScanNotCompleted =>
log.error(s"Passive Scan did not complete within the configured duration: $patienceConfigTimeout seconds.")
ScanNotCompleted
}
}
else {
log.error(s"Test URL '$testUrl' did not proxy via ZAP. Check if the browser is configured correctly to proxy via ZAP.")
ScanNotCompleted
}
}

def triggerSpiderScan()(implicit zapContext: ZapContext): String = {
callZapApi("/json/spider/action/scan", "contextName" -> zapContext.name, "url" -> testUrl)
}
Expand All @@ -46,6 +61,12 @@ class ZapScan(zapClient: ZapClient) extends Eventually {
ScanNotCompleted
}

/*
/json/pscan/view/recordsToScan returns how many records left to Passive Scan. When it is 0, Passive Scan is completed.
Passive Scan occurs on two instances.
1. When Journey tests proxies requests via ZAP, passive scan is performed automatically.
2. When the test URL is crawled by ZAP (triggerSpiderScan()) , passive scan is performed again on the new requests and response.
*/
private def recordsToScanStatus: ScanStatus = {
val recordsLeftToScan = 0
val recordsToScan = retry(expectedResult = recordsLeftToScan) {
Expand Down Expand Up @@ -92,6 +113,17 @@ class ZapScan(zapClient: ZapClient) extends Eventually {
}
result
}

/*
Test URL should be proxied via ZAP for passive scan to be performed.
*/
private def isUrlProxiedViaZap: Boolean = {
val response = callZapApi("/json/core/view/urls", "baseurl" -> s"$testUrl")
val proxiedUrls: List[String] = (Json.parse(response) \ "urls").as[List[String]]
val testUrlPattern = testUrl + ".*"
proxiedUrls.exists(_.matches(testUrlPattern))
}

}

sealed trait ScanStatus
Expand Down
33 changes: 15 additions & 18 deletions src/main/twirl/report/index.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@
* limitations under the License.
*@

@import uk.gov.hmrc.zap.api.ZapAlert
@import uk.gov.hmrc.zap.api.ScanStatus

@import uk.gov.hmrc.zap.api.Scanner
@import uk.gov.hmrc.zap.ZapReport
@import play.api.libs.json.JsValue
@(alerts: List[ZapAlert], failureThreshold: String, spiderScanStatus: ScanStatus, activeScanStatus: ScanStatus,
missingScanners: List[Scanner], zapVersion: String)
@(zapReport: ZapReport)


<html>
<head>
Expand Down Expand Up @@ -92,17 +89,17 @@ <h1>
</h1>
<p>
</p>
<h2>ZAP Version: @{zapVersion}</h2>
<h2>ZAP Version: @{zapReport.zapVersion}</h2>
<div class="spacer-lg"></div>
@if(missingScanners.nonEmpty) {
@if(zapReport.missingScanners.nonEmpty) {
<h3 id="missing-scanners-h3"> Scanners not Configured</h3>
<table width="45%" id="missing-scanners-header" class="summary">
<tr bgcolor="#666666">
<th width="25%" height="24">ScannerId</th>
<th width="50%" align="center">Name</th>
<th width="25%" align="center">Scanner Type</th>
</tr>
@for(scanner <- missingScanners) {
@for(scanner <- zapReport.missingScanners) {
<tr bgcolor="#e8e8e8" type = "missing-scanners">
<td width="25%" height="24" align="left">@{scanner.id}</td>
<td width="50%" align="left">@{scanner.name}</td>
Expand All @@ -121,16 +118,16 @@ <h3>Summary of Alerts</h3>
Level</th><th width="55%" align="center">Number of Alerts</th>
</tr>
<tr bgcolor="#e8e8e8">
<td><a href="#high">High</a></td><td id="summary-high-count" align="center">@{alerts.count {_.risk == "High"}}</td>
<td><a href="#high">High</a></td><td id="summary-high-count" align="center">@{zapReport.relevantAlerts.count {_.risk == "High"}}</td>
</tr>
<tr bgcolor="#e8e8e8">
<td><a href="#medium">Medium</a></td><td id="summary-medium-count" align="center">@{alerts.count {_.risk == "Medium"}}</td>
<td><a href="#medium">Medium</a></td><td id="summary-medium-count" align="center">@{zapReport.relevantAlerts.count {_.risk == "Medium"}}</td>
</tr>
<tr bgcolor="#e8e8e8">
<td><a href="#low">Low</a></td><td id="summary-low-count" align="center">@{alerts.count {_.risk == "Low"}}</td>
<td><a href="#low">Low</a></td><td id="summary-low-count" align="center">@{zapReport.relevantAlerts.count {_.risk == "Low"}}</td>
</tr>
<tr bgcolor="#e8e8e8">
<td><a href="#info">Informational</a></td><td id="summary-info-count" align="center">@{alerts.count {_.riskShortName() == "Info"}}</td>
<td><a href="#info">Informational</a></td><td id="summary-info-count" align="center">@{zapReport.relevantAlerts.count {_.riskShortName() == "Info"}}</td>
</tr>
</table>
<div class="spacer-lg"></div>
Expand All @@ -140,20 +137,20 @@ <h3>Summary of Scan</h3>
<th width="45%" height="24">Scan</th><th width="55%" align="center">Status</th>
</tr>
<tr bgcolor="#e8e8e8">
<td>Passive</td><td id="passive-scan" align="center">Run</td>
<td>Passive</td><td id="passive-scan" align="center">@{zapReport.passiveScanStatus.toString}</td>
</tr>
<tr bgcolor="#e8e8e8">
<td>Spider</td><td id="spider-scan" align="center">@{spiderScanStatus.toString}</td>
<td>Spider</td><td id="spider-scan" align="center">@{zapReport.spiderScanStatus.toString}</td>
</tr>
<tr bgcolor="#e8e8e8">
<td>Active</td><td id="active-scan" align="center">@{activeScanStatus.toString}</td>
<td>Active</td><td id="active-scan" align="center">@{zapReport.activeScanStatus.toString}</td>
</tr>
</table>
<div class="spacer-lg"></div>
<h3>Failure Threshold: @{failureThreshold}</h3>
<h3>Failure Threshold: @{zapReport.failureThreshold}</h3>
<div class="spacer-lg"></div>
<h3>Alert Details</h3>
@for(a <- alerts) {
@for(a <- zapReport.relevantAlerts) {
<div class="spacer"></div>
<table id="alert-@{a.id}" type="alert-details" result="@{a.id}" width="100%" class="results">
<tr height="24" class="risk-@{a.riskShortName().toLowerCase}"><a name="@{a.riskShortName().toLowerCase}"></a><th width="10%">@{a.risk} (@{a.confidence})</th>
Expand Down
87 changes: 68 additions & 19 deletions src/test/scala/uk/gov/hmrc/ZapReportSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package uk.gov.hmrc

import com.typesafe.config.{Config, ConfigFactory}
import play.api.libs.json.{Json, Reads}
import uk.gov.hmrc.zap.ZapReport
import uk.gov.hmrc.zap.ZapReport._
import uk.gov.hmrc.zap.api.{ScanCompleted, ScanNotCompleted, Scanner, ZapAlert}
import uk.gov.hmrc.zap.client.HttpClient
Expand All @@ -41,15 +42,27 @@ class ZapReportSpec extends BaseSpec {

"html report" should {
"should contain the failure threshold " in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, threshold, spiderScanStatus = ScanCompleted,
activeScanStatus = ScanNotCompleted, missingScanners, zapVersion)
val zapReport = ZapReport(alerts,
threshold,
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanNotCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)

reportHtmlAsString should include("AUniqueThreshold")
}

"should contain the correct alert count by risk in the Summary of Alerts table" in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, "AUniqueThreshold",
spiderScanStatus = ScanCompleted, activeScanStatus = ScanNotCompleted, missingScanners, zapVersion)
val zapReport = ZapReport(alerts,
"AUniqueThreshold",
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanNotCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)
val reportXml: Elem = XML.loadString(reportHtmlAsString)

getByAtt(reportXml, "id", "summary-high-count").text shouldBe "1"
Expand All @@ -58,19 +71,31 @@ class ZapReportSpec extends BaseSpec {
getByAtt(reportXml, "id", "summary-info-count").text shouldBe "1"
}

"should show the correct scan status in the Summary of Scan table when spiderScan and activeScan is not completed" in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, "AUniqueThreshold",
spiderScanStatus = ScanNotCompleted, activeScanStatus = ScanNotCompleted, missingScanners, zapVersion)
"should show the correct scan status in the Summary of Scan table when passiveScan, spiderScan and activeScan is not completed" in new TestSetup {
val zapReport = ZapReport(alerts,
"AUniqueThreshold",
passiveScanStatus = ScanNotCompleted,
spiderScanStatus = ScanNotCompleted,
activeScanStatus = ScanNotCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)
val reportXml: Elem = XML.loadString(reportHtmlAsString)

getByAtt(reportXml, "id", "passive-scan").text shouldBe "Run"
getByAtt(reportXml, "id", "passive-scan").text shouldBe "Not Run"
getByAtt(reportXml, "id", "spider-scan").text shouldBe "Not Run"
getByAtt(reportXml, "id", "active-scan").text shouldBe "Not Run"
}

"should show the correct scan status in the Summary of Scan table when spiderScan and ActiveScan is completed" in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, "AUniqueThreshold",
spiderScanStatus = ScanCompleted, activeScanStatus = ScanCompleted, missingScanners, zapVersion)
"should show the correct scan status in the Summary of Scan table when passiveScan, spiderScan and activeScan is completed" in new TestSetup {
val zapReport = ZapReport(alerts,
"AUniqueThreshold",
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)
val reportXml: Elem = XML.loadString(reportHtmlAsString)

getByAtt(reportXml, "id", "passive-scan").text shouldBe "Run"
Expand All @@ -79,8 +104,14 @@ class ZapReportSpec extends BaseSpec {
}

"should display the details of 4 alerts" in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, "AUniqueThreshold",
spiderScanStatus = ScanCompleted, activeScanStatus = ScanCompleted, missingScanners, zapVersion)
val zapReport = ZapReport(alerts,
"AUniqueThreshold",
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)
val reportXml: Elem = XML.loadString(reportHtmlAsString)

getByAtt(reportXml, "type", "alert-details").size shouldBe 4
Expand All @@ -90,8 +121,14 @@ class ZapReportSpec extends BaseSpec {
override val missingScanners: List[Scanner] =
List(Scanner("9999", "TestScanner1", "Passive Scan"), Scanner("10000", "TestScanner2", "Passive Scan"))

val reportHtmlAsString: String = generateHtmlReport(alerts, "AUniqueThreshold",
spiderScanStatus = ScanCompleted, activeScanStatus = ScanCompleted, missingScanners, zapVersion)
val zapReport = ZapReport(alerts,
"AUniqueThreshold",
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)
val reportXml: Elem = XML.loadString(reportHtmlAsString)

getByAtt(reportXml, "id", "missing-scanners-h3").size shouldBe 1
Expand All @@ -100,8 +137,14 @@ class ZapReportSpec extends BaseSpec {
}

"should not display missing scanners list when all required scanners configured" in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, "AUniqueThreshold",
spiderScanStatus = ScanCompleted, activeScanStatus = ScanCompleted, missingScanners, zapVersion)
val zapReport = ZapReport(alerts,
"AUniqueThreshold",
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)
val reportXml: Elem = XML.loadString(reportHtmlAsString)

getByAtt(reportXml, "type", "missing-scanners").size shouldBe 0
Expand All @@ -110,8 +153,14 @@ class ZapReportSpec extends BaseSpec {
}

"should contain ZAP version" in new TestSetup {
val reportHtmlAsString: String = generateHtmlReport(alerts, threshold, spiderScanStatus = ScanCompleted,
activeScanStatus = ScanNotCompleted, missingScanners, zapVersion)
val zapReport = ZapReport(alerts,
threshold,
passiveScanStatus = ScanCompleted,
spiderScanStatus = ScanCompleted,
activeScanStatus = ScanNotCompleted,
missingScanners,
zapVersion)
val reportHtmlAsString: String = generateHtmlReport(zapReport)

reportHtmlAsString should include(s"ZAP Version: $zapVersion")
}
Expand Down
40 changes: 39 additions & 1 deletion src/test/scala/uk/gov/hmrc/ZapScanSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package uk.gov.hmrc

import com.typesafe.config.{Config, ConfigFactory}
import org.mockito.Matchers.{any, eq => eqTo}
import org.mockito.Mockito.{verify, when}
import org.mockito.Mockito.{verify, when, atLeastOnce}
import org.scalatest.concurrent.Eventually
import uk.gov.hmrc.zap.api._
import uk.gov.hmrc.zap.client.{HttpClient, ZapClient}
Expand Down Expand Up @@ -118,4 +118,42 @@ class ZapScanSpec extends BaseSpec {
zapScan.activeScanStatus shouldBe ScanNotCompleted
}
}
"Passive Scan status" should {
"should return ScanCompleted if test url is proxied via ZAP and passive scan is completed within the configured duration" in new TestSetup {

import zapConfiguration._

when(httpClient.get(any(), eqTo("/json/core/view/urls"), any())).thenReturn((200,
"""{"urls":["http://localhost:1234/abc/de", "http://localhost:1234/abc/def", "http://localhost:1234/abc/def/ghijk"]}""".stripMargin))
when(httpClient.get(any(), eqTo("/json/pscan/view/recordsToScan"), any())).thenReturn((200, """{"recordsToScan": "0"}"""))

zapScan.passiveScanStatus shouldBe ScanCompleted
verify(httpClient).get(zapBaseUrl, "/json/core/view/urls", "baseurl" -> testUrl)
verify(httpClient).get(zapBaseUrl, "/json/pscan/view/recordsToScan")
}

"should return ScanNotCompleted if test url is NOT proxied via ZAP" in new TestSetup {

import zapConfiguration._

when(httpClient.get(any(), eqTo("/json/core/view/urls"), any())).thenReturn((200,
"""{"urls":["http://localhost:1234/abc/de"]}""".stripMargin))

zapScan.passiveScanStatus shouldBe ScanNotCompleted
verify(httpClient).get(zapBaseUrl, "/json/core/view/urls", "baseurl" -> testUrl)
}

"should return ScanNotCompleted when test url proxied via ZAP but passive scan is NOT completed within the configured duration" in new TestSetup {

import zapConfiguration._

when(httpClient.get(any(), eqTo("/json/core/view/urls"), any())).thenReturn((200,
"""{"urls":["http://localhost:1234/abc/de", "http://localhost:1234/abc/def/ghijk"]}""".stripMargin))
when(httpClient.get(any(), eqTo("/json/pscan/view/recordsToScan"), any())).thenReturn((200, """{"recordsToScan": "1"}"""))

zapScan.passiveScanStatus shouldBe ScanNotCompleted
verify(httpClient).get(zapBaseUrl, "/json/core/view/urls", "baseurl" -> testUrl)
verify(httpClient, atLeastOnce()).get(zapBaseUrl, "/json/pscan/view/recordsToScan")
}
}
}

0 comments on commit ecd0a16

Please sign in to comment.