Scout Suite reports using Azure DevOps Pipeline

In this blog post, I am going to show how to run ScoutSuite in an Azure DevOps Pipeline and display reports within an Azure Storage Account Static Website

What is Scout Suite?

https://github.com/nccgroup/ScoutSuite

“Scout Suite is an open source multi-cloud security-auditing tool, which enables security posture assessment of cloud environments. Using the APIs exposed by cloud providers, Scout Suite gathers configuration data for manual inspection and highlights risk areas. Rather than going through dozens of pages on the web consoles, Scout Suite presents a clear view of the attack surface automatically.”

Scout Suite security-auditing tool that supports multiple cloud provides including Microsoft Azure!

Installation of Scout Suite is documented here in their Wiki , they also offer a Docker container – which in this blog I will be running!

Configuring Scout Suite for Microsoft Azure

The Azure related setup requirements are here

In this blog example I am going to be using a service principal

Summary of Azure DevOps Pipeline

I am going to be running Scout Suite security-auditing tool and outputting its report to an Azure Storage Account static website; their is some third-party html extensions for Azure DevOps but due to the report being a dynamic html page it doesn’t display correctly.

Prior to running pipeline

Azure Key Vault

With the service principal I created during my configuration setup within Microsoft Azure I am going to store the details of the service principal in Azure Key Vault

Secret names:-

CLIENT-ID :- Client ID of Service Principal
CLIENT-SECRET :- Service Principal Secret
TENANT-ID :- Azure AD Tenant ID of Service Principal

Configure an access policy for the service principal to allow it to retrieve these secrets from within the pipeline

Azure Storage Account Static Website

Create & configure an Azure Storage Account Static Website, I blogged how to create this previously

Additionally, due to it being a potential sensitive report, configure the storage account firewall also 🙂

Prior to running pipeline work now completed, lets look at the pipeline!

Azure DevOps Pipeline

task: AzureKeyVault@1:- Retrieves the relevant secrets using the managed identity

      - task: AzureKeyVault@1
        displayName: "Retrieve Key Vault Secrets"
        inputs:
          azureSubscription: 'tamopstf2'
          keyVaultName: 'tamopsscoutkv'
          secretsFilter: '*'
          runAsPreJob: false

task: AzureCCLI@2:- Adds Azure DevOps agent IP to Storage Account

      - task: AzureCLI@2
        displayName: "Add Azure DevOps IP to Storage Account"
        inputs:
          azureSubscription: 'tamopstf2'
          scriptType: 'bash'
          scriptLocation: inlineScript
          inlineScript: |
            IP=($(curl -s http://ipinfo.io/json | jq '.ip' | sed -e 's/^"//' -e 's/"$//'))
            az storage account network-rule add -g tamops-scoutsuite --account-name tamopsscoutsuite --ip-address $IP

task: Bash@3:- Run Scout Suite from Docker image

  • Referencing Scout Suite to run using the Service Principal credentials created earlier
  • Outputs report to /share that is mounted using -v, in this example the directory is $(DATE) that is a variable passed into Azure DevOps pipeline
      - task: Bash@3
        displayName: "Run Scout Suite"
        inputs:
          targetType: 'inline'
          script: |
            mkdir $(DATE)
            cd $(DATE)
            docker run --rm -v $(pwd):/share rossja/ncc-scoutsuite:0.6.0 /root/scoutsuite/bin/scout azure --client-id $CLIENT_ID --client-secret $CLIENT_SECRET --tenant $TENANT_ID --service-principal --all-subscriptions --no-browser --report-dir /share --report-name index
            echo "test"
        env:
          CLIENT_ID: $(CLIENT-ID)
          CLIENT_SECRET: $(CLIENT-SECRET)
          TENANT_ID: $(TENANT-ID)

task: AzureCCLI@2:- Copies Scout Suite Report created in above task to blob of Azure Storage Account Static Website

      - task: AzureCLI@2
        displayName: "Copy Scout Suite Report"
        inputs:
          azureSubscription: 'tamopstf2'
          scriptType: 'bash'
          scriptLocation: inlineScript
          inlineScript: |
            az storage copy -s $(DATE) --destination-account-name tamopsscoutsuite --destination-container '$web' --recursive

task: AzureCCLI@2:- Remove Azure DevOps IP from Storage Account

      - task: AzureCLI@2
        displayName: "Remove Azure DevOps IP from Storage Account"
        inputs:
          azureSubscription: 'tamopstf2'
          scriptType: 'bash'
          scriptLocation: inlineScript
          inlineScript: |
            # az --version
            # az account list
            IP=($(curl -s http://ipinfo.io/json | jq '.ip' | sed -e 's/^"//' -e 's/"$//'))
            az storage account network-rule remove -g tamops-scoutsuite --account-name tamopsscoutsuite --ip-address $IP

Full pipeline

name: $(BuildDefinitionName)_$(date:yyyyMMdd)$(rev:.r)

trigger:
  batch: true 
  branches:
    include:
      - main

variables: 
  DATE: '05042021'

pr: none

stages :
  - stage: scoutsuite
    jobs:
    - job: scoutsuite
      continueOnError: false
      steps:

      - task: AzureKeyVault@1
        displayName: "Retrieve Key Vault Secrets"
        inputs:
          azureSubscription: 'tamopstf2'
          keyVaultName: 'tamopsscoutkv'
          secretsFilter: '*'
          runAsPreJob: false

      - task: AzureCLI@2
        displayName: "Add Azure DevOps IP to Storage Account"
        inputs:
          azureSubscription: 'tamopstf2'
          scriptType: 'bash'
          scriptLocation: inlineScript
          inlineScript: |
            IP=($(curl -s http://ipinfo.io/json | jq '.ip' | sed -e 's/^"//' -e 's/"$//'))
            az storage account network-rule add -g tamops-scoutsuite --account-name tamopsscoutsuite --ip-address $IP

      - task: Bash@3
        displayName: "Run Scout Suite"
        inputs:
          targetType: 'inline'
          script: |
            mkdir $(DATE)
            cd $(DATE)
            docker run --rm -v $(pwd):/share rossja/ncc-scoutsuite:0.6.0 /root/scoutsuite/bin/scout azure --client-id $CLIENT_ID --client-secret $CLIENT_SECRET --tenant $TENANT_ID --service-principal --all-subscriptions --no-browser --report-dir /share --report-name index
            echo "test"
        env:
          CLIENT_ID: $(CLIENT-ID)
          CLIENT_SECRET: $(CLIENT-SECRET)
          TENANT_ID: $(TENANT-ID)

      - task: AzureCLI@2
        displayName: "Copy Scout Suite Report"
        inputs:
          azureSubscription: 'tamopstf2'
          scriptType: 'bash'
          scriptLocation: inlineScript
          inlineScript: |
            az storage copy -s $(DATE) --destination-account-name tamopsscoutsuite --destination-container '$web' --recursive

      - task: AzureCLI@2
        displayName: "Remove Azure DevOps IP from Storage Account"
        inputs:
          azureSubscription: 'tamopstf2'
          scriptType: 'bash'
          scriptLocation: inlineScript
          inlineScript: |
            # az --version
            # az account list
            IP=($(curl -s http://ipinfo.io/json | jq '.ip' | sed -e 's/^"//' -e 's/"$//'))
            az storage account network-rule remove -g tamops-scoutsuite --account-name tamopsscoutsuite --ip-address $IP

Running in Azure Devops, we can see each task referenced

Reviewing the Storage Account blog storage to where the static website was created, a new folder has been created with the required Scout Suite report

Finally, view the report!

GitHub repository here

Leave a Reply