Skip to content
name: Sign PowerShell Scripts and Deploy to Test environment
on:
workflow_dispatch:
push:
env:
ARTIFACT_NAME: PowerShell.Workflows.ScriptSigning
jobs:
sign_scripts:
name: Sign, validate, test and publish PowerShell scripts as pipeline artifacts
runs-on: ubuntu-latest
steps:
- name: Install PowerShell
uses: microsoft/setup-powershell@v1
- name: Import code signing certificate
shell: pwsh
run: |
$pfxCertFilePath = Join-Path -Path $PSScriptRoot -ChildPath "CodeSigningCertificate.pfx"
Set-Content -Value $([System.Convert]::FromBase64String($env:BASE64_PFX)) -Path $pfxCertFilePath -Encoding Byte
$codeSigningCert = Import-PfxCertificate -FilePath $pfxCertFilePath -Password $($env:PFX_PASSWORD | ConvertTo-SecureString -AsPlainText -Force) -CertStoreLocation ./cert/codesingingcert/
env:
BASE64_PFX: ${{ secrets.BASE64_PFX }}
PFX_PASSWORD: ${{ secrets.PFX_PASSWORD }}
- name: Check out repository
uses: actions/checkout@v2
- name: Sign pwsh scripts and modules
shell: pwsh
run: |
# remove git dir from checked out repo
Get-ChildItem -Path "." -Filter ".git*" -Force | ForEach-Object {Remove-Item -Path $_.FullName -Recurse -Force}
$scripts = Get-ChildItem -Path . -Include *.ps1,*.psm1,*.psd1 -Recurse -ErrorAction Stop
# load cert
$codeSigningCert = Get-ChildItem ./cert/codesingingcert | Select-Object -First 1
foreach ($script in $scripts) {
try {
$scriptContent = Get-Content -Path $script.FullName
Write-Output "Signing script `"$($script.Name)`" with certificate `"$($codeSigningCert.Thumbprint)`""
# sign script
$null = Set-AuthenticodeSignature -Certificate $codeSigningCert -FilePath $script.FullName -TimestampServer "http://timestamp.comodoca.com/rfc3161"
}
catch {
Write-Error $_
}
}
- name: Validate Signature
shell: pwsh
run: |
$signatureStatuses = Get-ChildItem -r -i *.ps* | Get-AuthenticodeSignature
Foreach ($signatureStatus in $signatureStatuses) {
If ($signatureStatus.Status -eq 'HashMismatch') {
throw "File '$($signatureStatus.Path)' has a hash status of '$($signatureStatus.status)'"
}
ElseIf ($signatureStatus.Status -eq 'NotSigned') {
Write-Warning "File '$($signatureStatus.Path)' has a hash status of '$($signatureStatus.status)'"
}
ElseIf ($signatureStatus.Status -eq 'Valid') {
Write-Host "File '$($signatureStatus.Path)' has a hash status of '$($signatureStatus.status)'"
}
Else {
throw "File '$($signatureStatus.Path)' has an unhandled hash status of '$($signatureStatus.status)'"
}
}
- name: Test Module Imports
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$moduleFiles = Get-ChildItem -path ./* -recurse -include *.psm1
Write-Host "Count of module files: $($moduleFiles.count)"
try {
ForEach ($moduleFile in $moduleFiles) {
Import-Module $moduleFile.Fullname -ErrorAction Stop
}
}
catch {
throw "Failed test import module '$moduleFile' with error: $_"
}
$importedModules = Get-Module
Write-Host "Imported modules: `n $($importedModules.Path | Out-String)"
$missingModules = $moduleFiles | Where-object {$_ -inotin ($importedModules).Path}
If ($missingModules) {
throw "The following modules failed import test: $missingModules"
}
- name: Zip Signed Modules
shell: pwsh
run: |
$moduleCodeFilesObjs = Get-ChildItem -Path .\src -Recurse -Include *.psm1 -Exclude '*-GSA*','*GuardrailsSolutionAcceleratorSetup*','*Deploy-GuardrailsSolutionAccelerator*'
Write-Host "'$($moduleCodeFilesObjs.count)' module manifest files "
ForEach ($moduleCodeFile in $moduleCodeFilesObjs) {
$moduleManifestFile = Get-Item -Path $moduleCodeFile.FullName.replace('psm1','psd1')
If ($moduleCodeFilesObjs.FullName -icontains $moduleCodeFile.FullName -or $moduleCodeFilesObjs.FullName -icontains $moduleManifestFile.FullName) {
Write-Host "Module '$($moduleCodeFile.BaseName)' found, zipping module files..."
$destPath = "./psmodules/$($moduleCodeFile.BaseName).zip"
If ($moduleCodeFile.DIrectory.Name -eq 'Guardrails-Localization') {
Compress-Archive -Path "$($moduleCodeFile.Directory)/*" -DestinationPath $destPath -Force
}
Else {
$filesToZip = $moduleManifestFile,$moduleCodeFile
Compress-Archive -Path $filesToZip -DestinationPath $destPath -Force
}
}
Else {
Write-Host "Neither the manifest '$($moduleCodeFile.FullName.toLower())' or script file '$($moduleManifestFile.FullName.ToLower())' for module '$($moduleCodeFile.BaseName)' was changed, skipping zipping..."
}
}
- name: Publish artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.ARTIFACT_NAME }}
path: ./psmodules/*.zip
deploy:
name: Deploy CAC to a tenant
runs-on: ubuntu-latest
steps:
- name: Check Out
uses: actions/checkout@v3
- name: Download zipped modules and replace old ones
uses: actions/download-artifact@v3
with:
name: ${{ env.ARTIFACT_NAME }}
path: ./psmodules
- name: Login to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
enable-AzPSSession: true
- name: Stage zipped/signed modules in Storage Account
uses: azure/powershell@v1
with:
inlineScript: |
$storageContext = (Get-AzStorageAccount -ResourceGroupName $env:PIPELINEMODULESTAGING_RGNAME -Name $env:PIPELINEMODULESTAGING_STORAGEACCOUNTNAME).Context
$zippedModules = Get-ChildItem -Path ./psmodules/* -Include *.zip -File
ForEach ($moduleZip in $zippedModules) {
Set-AzStorageBlobContent -Context $storageContext -Container psmodules -File $moduleZip.FullName -Blob $moduleZip.Name -Force -ErrorAction Stop
}
azPSVersion: "latest"
env:
PIPELINEMODULESTAGING_RGNAME: ${{ vars.PIPELINEMODULESTAGING_RGNAME }}

Check failure on line 149 in .github/workflows/sign-scripts-development.yml

View workflow run for this annotation

GitHub Actions / Sign PowerShell Scripts and Deploy to Test environment

Invalid workflow file

The workflow is not valid. .github/workflows/sign-scripts-development.yml (Line: 149, Col: 13): A mapping was not expected .github/workflows/sign-scripts-development.yml (Line: 206, Col: 13): A mapping was not expected
PIPELINEMODULESTAGING_STORAGEACCOUNTNAME: ${{ vars.ORGANIZATION_VAR }}
- name: Pre-Clean Test environment
uses: azure/powershell@v1
with:
inlineScript: |
ipmo ./src/GuardrailsSolutionAcceleratorSetup
$configFilePath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'config.json'
$configContent = @'
{
"keyVaultName": "gsapipe",
"resourcegroup": "gsapipe",
"region": "CanadaCentral",
"storageaccountName": "gsapipe",
"logAnalyticsworkspaceName": "gsapipe",
"autoMationAccountName": "gsapipe",
"FirstBreakGlassAccountUPN": "breakglass1@$env:TESTTENANT_DOMAIN",
"SecondBreakGlassAccountUPN": "breakglass2@$env:TESTTENANT_DOMAIN",
"PBMMPolicyID": "4c4a5f27-de81-430b-b4e5-9cbd50595a87",
"AllowedLocationPolicyId": "e56962a6-4747-49cd-b67b-bf8b01975c4c",
"DepartmentNumber": "190",
"CBSSubscriptionName": "$env:CBSSUBSCRIPTION_NAME",
"SecurityLAWResourceId": "/subscriptions/$env:TESTSUBSCRIPTION_ID/resourceGroups/rg-core/providers/Microsoft.OperationalInsights/workspaces/mtb-law01",
"HealthLAWResourceId": "/subscriptions/$env:TESTSUBSCRIPTION_ID/resourceGroups/rg-core/providers/Microsoft.OperationalInsights/workspaces/mtb-law01",
"Locale": "en-CA",
"lighthouseServiceProviderTenantID": "$env:LIGHTHOUSEPROVIDER_TENANTID",
"lighthousePrincipalDisplayName": "SSC CSPM TEAM",
"lighthousePrincipalId": "$env:LIGHTHOUSEPROVIDER_PRINCIPALID",
"lighthouseTargetManagementGroupID": "mb_co",
"subscriptionId": "$env:TESTSUBSCRIPTION_ID",
"SSCReadOnlyServicePrincipalNameAPPID": "00000000-0000-0000-0000-000000000000",
"uniqueNameSuffix": "$env:UNIQUENAME_SUFFIX",
"securityRetentionDays": "730",
"cloudUsageProfiles": "1,2"
}
'@
Set-Content -Path $configFilePath -Value $configContent
Push-Location -Path setup
try {
$ErrorActionPreference = 'Stop'
remove-gsacentralizedReportingCustomerComponents -Force -configFilePath $configFilePath
Remove-GSACentralizedDefenderCustomerComponents -Force -configFilePath $configFilePath
Remove-GSACoreResources -Force -Wait -configFilePath $configFilePath
}
catch {
throw "Failed test deploy of solution with error: $_"
}
finally {
If (!$?) {throw "Failed test deploy of solution with error: $($error[0]) $_"}
Pop-Location
}
azPSVersion: "latest"
env:
TESTTENANT_DOMAIN: ${{ vars.TESTTENANT_DOMAIN }}
TESTSUBSCRIPTION_ID: ${{ vars.TESTSUBSCRIPTION_ID }}
CBSSUBSCRIPTION_NAME: ${{ vars.CBSSUBSCRIPTION_NAME }}
TESTSUBSCRIPTION_ID: ${{ vars.TESTSUBSCRIPTION_ID }}
LIGHTHOUSEPROVIDER_TENANTID: ${{ vars.LIGHTHOUSEPROVIDER_TENANTID }}
LIGHTHOUSEPROVIDER_PRINCIPALID: ${{ vars.LIGHTHOUSEPROVIDER_PRINCIPALID }}
UNIQUENAME_SUFFIX: ${{ vars.UNIQUENAME_SUFFIX }}
- name: Deploy Test environment
uses: azure/powershell@v1
with:
inlineScript: |
ipmo ./src/GuardrailsSolutionAcceleratorSetup
$c = Get-GSAExportedConfig -KeyVaultName gsapipe-$env:UNIQUENAME_SUFFIX -y
$config = $c.configString | ConvertFrom-Json
Write-Output "Waiting for 'main' and 'backend' runbook jobs to complete (up to 5 mins)"
$timeout = New-TimeSpan -Minutes 5
$timer = [System.Diagnostics.Stopwatch]::StartNew()
do {
$jobMain = Get-AzAutomationJob -RunbookName 'main' -ResourceGroupName $config.runtime.resourceGroup -AutomationAccountName $config.runtime.automationAccountName |
Sort-Object StartTIme -Descending |
Select-Object -First 1
$jobBackend = Get-AzAutomationJob -RunbookName 'backend' -ResourceGroupName $config.runtime.resourceGroup -AutomationAccountName $config.runtime.automationAccountName |
Sort-Object StartTIme -Descending |
Select-Object -First 1
Start-Sleep 1
}
until (($jobMain.Status -in 'Completed','Failed' -and $jobBackend -in 'Completed','Failed') -or ($timer.Elapsed -ge $timeout))
If ($jobMain.Status -eq 'Failed') {
throw "main runbook failed to execute"
}
If ($jobMain.Status -eq 'Completed') {
Write-Output "'main' runbook completed successfully, checking for errors in output. "
}
If ($jobBackend.Status -eq 'Failed') {
throw "backend runbook failed to execute"
}
If ($jobBackend.Status -eq 'Completed') {
Write-Output "'backend' runbook completed successfully, checking for errors in output. "
}
$jobMainOutput = Get-AzAutomationJobOutput -Id $jobMain.JobId -ResourceGroupName $config.runtime.resourceGroup -AutomationAccountName $config.runtime.automationAccountName -Stream 'Error'
$jobBackendOutput = Get-AzAutomationJobOutput -Id $jobBackend.JobId -ResourceGroupName $config.runtime.resourceGroup -AutomationAccountName $config.runtime.automationAccountName -Stream 'Error'
$errorsFound = $false
ForEach ($outputRecord in $jobMainOutput) {
If ($outputRecord.Summary -like 'Failed invoke the module execution script for module*') {
throw 'Errors found in "main" runbook Azure Automation jobs'
}
}
ForEach ($outputRecord in $jobBackendOutput) {
If ($outputRecord.Summary -like 'Failed invoke the module execution script for module*') {
throw 'Errors found in "backend" runbook Azure Automation jobs'
}
}
azPSVersion: "latest"
env:
UNIQUENAME_SUFFIX: ${{ vars.UNIQUENAME_SUFFIX }}