A DevSecOps approach will aid in curbing project costs.

We’re all well-acquainted with the concept of security incidents, and it’s become a matter of “when” rather than “if” nowadays. In the past, we used to discuss our development life cycle, but gradually we realized the need to bring the operations teams closer. This led to the creation of DevOps teams, a well-known and widely used concept today. However, this approach has been outdated for quite some time within organizations. For an effective program or project, security must be incorporated as early as possible in their solutions. Numerous solutions can help organizations integrate security from the beginning by involving security teams earlier in the project. For instance, Securemation offers the “Secure by Design” service, which enables organizations to identify threats during the design phase of the project. Nevertheless, this post will primarily focus on the development phase of the project and how security can lead to cost avoidance and reduction. The traditional DevOps framework involves developing the solution first and conducting security tests at the end. However, this approach poses a problem: the security team may discover security threats deeply embedded in the core of your solution. If these security threats are tied to core functionalities, the entire solution might need to be redeveloped from scratch, significantly escalating project costs. DevSecOps, on the other hand, ensures that security tests and feedback are provided continuously, enabling the identification of security concerns at the earliest stage possible and responding proactively. The DevSecOps approach can be utilized to mitigate threats in technology, people, and processes. This post will provide an in-depth analysis of how organizations using Microsoft Azure DevOps can apply this framework in their pipelines. To offer early feedback to the development team, we will leverage the following solutions:
  • Software Composition Analysis (SCA): At this stage, we will ensure that the libraries we use in our project are secure and licence compliant (e.g., identifying vulnerable library versions or non-commercial libraries used in commercial applications).
  • Static Application Security Test (SAST): During this stage, we will ensure that the code at rest in your repository does not contain any security vulnerabilities (e.g., authentication secrets in code or vulnerabilities introduced by poor code standards).
  • Dynamic Application Security Test (DAST): In this stage, we will ensure that the code running on a server, while “alive,” does not have any security vulnerabilities.

SCA

For this example, we will use Mend Bolt to perform the analysis of the libraries and deliver a report in the build pipeline to the developers’ team.

The following code will allow you to add a task to perform the SCA analysis:

				
					- stage: SAST
  displayName: SAST stage
  jobs:
  - job: SCA
    steps:
    - task: WhiteSource@21
      inputs:
        cwd: '$(System.DefaultWorkingDirectory)'
				
			

Once the pipeline is finalised, you will have a report similar to the following:

SAST

The SAST stage will leverage SonarQube that has a community version that can be used in your environment. We will not cover how to setup your server in this post, and I will assume you already have it up and linked with you Microsoft Azure DevOps instance. The following code will add another task to your build pipeline and submit your code to the analysis process of SonarQube.

				
					- stage: SAST
  displayName: SAST stage
 - job: SAST
   steps:
   - task: SonarQubePrepare@5
     inputs:
       SonarQube: 'Server connection'
       scannerMode: 'CLI'
       configMode: 'manual'
       cliProjectKey: 'Project Key'
       cliSources: '.'
   - task: SonarQubeAnalyze@5
   - task: SonarQubePublish@5
     inputs:
       pollingTimeoutSec: '300'
				
			

Once the pipeline is finalised, you will have a report similar to the following:

DAST

The final stage of the pipeline DevSecOps approach will be adding the OWASP ZAP container to perform dynamic tests once your application is deployed and is running. The following code will add the DAST task to your release pipeline:

  • Install Docker (Docker CLI installer)

  • OWASP ZAP Image (Bash Task)
				
					# Download OWASP ZAP v2.12.0
echo 'Get OWASP ZAP v2.12.0'
docker pull owasp/zap2docker-stable:2.12.0

				
			
  • OWASP ZAP Full Scan (Bash Task)
				
					# Full Scan 
echo 'Full Scan https://YOUR_APPLICATION.com.au/'
chmod -R 777 ./
docker run --rm -v $(System.DefaultWorkingDirectory):/zap/wrk/:rw -t owasp/zap2docker-stable:2.12.0 zap-full-scan.py -t https://YOUR_APPLICATION.com.au/ -g gen.conf -r report-full.html -x report-full.xml >> report-full.txt
true
				
			
  • Translate XML to NUnit (PowerShell)
				
					$XSLT = '<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="NumberOfItems" select="count(OWASPZAPReport/site/alerts/alertitem)"/>
<xsl:variable name="generatedDateTime" select="OWASPZAPReport/generated"/>
<xsl:template match="/">
<test-run id="1" name="OWASPReport" fullname="OWASPConvertReport" testcasecount="" result="Failed" total="{$NumberOfItems}" passed="0" failed="{$NumberOfItems}" inconclusive="0" skipped="0" asserts="{$NumberOfItems}" engine-version="3.9.0.0" clr-version="4.0.30319.42000" start-time="{$generatedDateTime}" end-time="{$generatedDateTime}" duration="0">
<command-line>a</command-line>
<test-suite type="Assembly" id="0-1005" name="OWASP" fullname="OWASP" runstate="Runnable" testcasecount="{$NumberOfItems}" result="Failed" site="Child" start-time="{$generatedDateTime}" end-time="{$generatedDateTime}" duration="0.352610" total="{$NumberOfItems}" passed="0" failed="{$NumberOfItems}" warnings="0" inconclusive="0" skipped="0" asserts="{$NumberOfItems}">
<environment framework-version="3.11.0.0" clr-version="4.0.30319.42000" os-version="Microsoft Windows NT 10.0.17763.0" platform="Win32NT" cwd="C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE" machine-name="Azure Hosted Agent" user="flacroix" user-domain="NORTHAMERICA" culture="en-US" uiculture="en-US" os-architecture="x86" />
<test-suite type="TestSuite" id="0-1004" name="UnitTestDemoTest" fullname="UnitTestDemoTest" runstate="Runnable" testcasecount="2" result="Failed" site="Child" start-time="2019-02-01 17:03:03Z" end-time="2019-02-01 17:03:04Z" duration="0.526290" total="2" passed="1" failed="1" warnings="0" inconclusive="0" skipped="0" asserts="1">
<test-suite type="TestFixture" id="0-1000" name="UnitTest1" fullname="UnitTestDemoTest.UnitTest1" classname="UnitTestDemoTest.UnitTest1" runstate="Runnable" testcasecount="2" result="Failed" site="Child" start-time="2019-02-01 17:03:03Z" end-time="2019-02-01 17:03:04Z" duration="0.495486" total="2" passed="1" failed="1" warnings="0" inconclusive="0" skipped="0" asserts="1">
<xsl:for-each select="OWASPZAPReport/site/alerts/alertitem">
<xsl:variable name="TestResult" select="count(OWASPZAPReport/site/alerts/alertitem/riskcode)"/>
<xsl:if test="riskcode &gt; 1">
<test-case id="0-1001" name="{name}" fullname="{name}" methodname="Stub" classname="UnitTestDemoTest.UnitTest1" runstate="NotRunnable" seed="400881240" result="Failed" label="Invalid" start-time="{$generatedDateTime}" end-time="{$generatedDateTime}" duration="0" asserts="0">
<failure>
<message>
<xsl:value-of select="desc"/>. 
<xsl:value-of select="solution"/>
</message>
<stack-trace>
<xsl:for-each select="instances/instance">
<xsl:value-of select="uri"/>, <xsl:value-of select="method"/>, <xsl:value-of select="param"/>,
</xsl:for-each>
</stack-trace>
</failure>
</test-case>
</xsl:if>
<xsl:if test="riskcode &lt; 2">
<test-case id="0-1001" name="{name}" fullname="{name}" methodname="Stub" classname="UnitTestDemoTest.UnitTest1" runstate="NotRunnable" seed="400881240" result="Skipped" label="Invalid" start-time="{$generatedDateTime}" end-time="{$generatedDateTime}" duration="0" asserts="0">
<failure>
<message>
<xsl:value-of select="desc"/>. 
<xsl:value-of select="solution"/>
</message>
<stack-trace>
<xsl:for-each select="instances/instance">
<xsl:value-of select="uri"/>, <xsl:value-of select="method"/>, <xsl:value-of select="param"/>,
</xsl:for-each>
</stack-trace>
</failure>
</test-case>
</xsl:if>
</xsl:for-each>
</test-suite>
</test-suite>
</test-suite>
</test-run>
</xsl:template>
</xsl:stylesheet>
'
# Create XSLT file
$Folder = Get-Location
$fileName = "OWASP-XML-2-Unit.xslt"
$XsltFilePath = Join-Path -Path $Folder -ChildPath $fileName
New-Item -ItemType File -Name $fileName -Force
$XSLT | Add-Content -Path $XsltFilePath
				
			
  • File Names (PowerShell)
				
					# filenames
$Folder = Get-Location
$fileName = "OWASP-XML-2-Unit.xslt"
$XsltFilePath = Join-Path -Path $Folder -ChildPath $fileName
$ScanReport = "report-full.xml"
$TestReport = "report-full-Tests.xml"
$XmlInputPath = Join-Path -Path $Folder -ChildPath $ScanReport
$XmlOutputPath = Join-Path -Path $Folder -ChildPath $TestReport


Write-Host $XsltFilePath
Write-Host $XmlInputPath
Write-Host $XmlOutputPath


# transform
$XslTransform = New-Object System.Xml.Xsl.XslCompiledTransform
$XslTransform.Load($XsltFilePath)
$XslTransform.Transform($XmlInputPath, $XmlOutputPath)


# Tests Folder
$TestFolderName = "tests"
Write-Host "create $TestFolderName"
if ((Test-Path -Path $TestFolderName) -eq $false) {
$testsFolder = New-Item -Name $TestFolderName -ItemType Directory
}


Copy-Item -Path $TestReport -Destination $TestFolderName
				
			
  • Add tests results to NUnit (PowerShell)
				
					# Add Passed tests from log to Nunit results
function Add-PassedTests {
[CmdletBinding()]
param (
[string]$LogFilename,
[string]$TestFilename,
[string]$OutputFile
)

# Test Case
$testCase = '<test-case id="{id}" name="{line}" fullname="{line}" methodname="Stub" classname="UnitTestDemoTest.UnitTest1" runstate="NotRunnable" seed="400881240" result="Passed" label="Invalid" start-time="" end-time="" duration="0" asserts="0">
<failure>
<message></message>
<stack-trace></stack-trace>
</failure>
</test-case>'



$TestsPassed = @()


foreach ($line in Get-Content $LogFilename) {
if (Select-String -InputObject $line -Pattern "^PASS:") {
$count++
$Name = $line.Replace("PASS: ", "")
$Name = $Name.Replace('"', "")
$IdCount = "1-" + $count
$testCase2 = $testCase.Replace("{line}", $Name)
$testCase2 = $testCase2.Replace("{id}", $IdCount)
$TestsPassed += $testCase2
}
}


New-Item -Path $OutputFile -ItemType File -Force
$FirstElement = $true
foreach ($line in Get-Content $TestFilename) {


if ($line -match "<test-case" -and $FirstElement) {
foreach ($item in $TestsPassed) {
$item | Add-Content -Path $OutputFile
}
$FirstElement = $false
$line | Add-Content -Path $OutputFile
}
else {
$line | Add-Content -Path $OutputFile
}
}
}


Add-PassedTests -LogFilename "report-full.txt" -TestFilename "report-full-Tests.xml" -OutputFile "report-full-Tests-2.xml"
Copy-Item -Path "report-full-Tests-2.xml" -Destination $(System.DefaultWorkingDirectory)\tests
				
			
  • Publish DAST Results (Publish Test Results)

Test result format:

				
					NUnit
				
			

Test results files:

				
					tests/*full*Tests-*.xml
				
			

Once the pipeline is finalised, you will have a report similar to the following:

Share the Post:
Related Articals

Customize Consent Preferences

We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below. The cookies that are categorized as “Necessary” are stored on your browser as they are essential for enabling the basic functionalities of the site.

We also use third-party cookies that help us analyze how you use this website, store your preferences, and provide the content and advertisements that are relevant to you. These cookies will only be stored in your browser with your prior consent.

 

You can choose to enable or disable some or all of these cookies but disabling some of them may affect your browsing experience.

Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.

No cookies to display.
Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.
No cookies to display.

Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.

No cookies to display.
Advertisement cookies are used to provide visitors with customized advertisements based on the pages you visited previously and to analyze the effectiveness of the ad campaigns.
No cookies to display.