Sign PowerShell Scripts and Deploy to Test environment #657
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Sign PowerShell Scripts and Deploy to Test environment | |
on: | |
workflow_dispatch: | |
push: | |
paths-ignore: | |
- 'README.md' | |
- 'SECURITY.md' | |
- 'SUPPORT.md' | |
- 'psmodules/**' | |
- 'docs/**' | |
branches: | |
- main | |
concurrency: | |
group: Deploy-Azure-CAC-Test | |
cancel-in-progress: false | |
permissions: | |
id-token: write | |
env: | |
ARTIFACT_NAME: PowerShell.Workflows.ScriptSigning | |
jobs: | |
sign_and_publish_scripts: | |
name: Sign, validate, test and publish PowerShell scripts as pipeline artifacts | |
runs-on: windows-2019 | |
environment: test | |
steps: | |
- name: Check out repository | |
uses: actions/checkout@v3 | |
- name: Test Module Imports | |
shell: powershell | |
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: powershell | |
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@v4 | |
with: | |
name: ${{ env.ARTIFACT_NAME }} | |
path: ./psmodules/*.zip | |
deploy: | |
name: Deploy CAC to a tenant | |
needs: sign_and_publish_scripts | |
runs-on: ubuntu-latest | |
environment: test | |
steps: | |
- name: Check Out | |
uses: actions/checkout@v3 | |
- name: Download zipped modules and replace old ones | |
uses: actions/download-artifact@v4 | |
with: | |
name: ${{ env.ARTIFACT_NAME }} | |
path: ./psmodules | |
- name: AZ Login | |
uses: azure/login@v2 | |
with: | |
creds: ${{ secrets.AZURE_CREDENTIALS }} | |
enable-AzPSSession: true | |
- name: Stage zipped/signed modules in Storage Account | |
uses: azure/powershell@v2 | |
with: | |
inlineScript: | | |
Set-AzContext -SubscriptionId $env:SUBSCRIPTIONID | |
$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 }} | |
PIPELINEMODULESTAGING_STORAGEACCOUNTNAME: ${{ vars.PIPELINEMODULESTAGING_SANAME }} | |
SUBSCRIPTIONID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
- name: Pre-Clean Test environment | |
uses: azure/powershell@v2 | |
continue-on-error: true | |
with: | |
inlineScript: | | |
Set-AzContext -SubscriptionId $env:TESTSUBSCRIPTION_ID | |
ipmo ./src/GuardrailsSolutionAcceleratorSetup | |
$configFilePath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'config.json' | |
$CBSSUBSCRIPTION_NAME = $env:CBSSUBSCRIPTION_NAME | |
$TESTSUBSCRIPTION_ID = $env:TESTSUBSCRIPTION_ID | |
$LIGHTHOUSEPROVIDER_TENANTID = $env:LIGHTHOUSEPROVIDER_TENANTID | |
$LIGHTHOUSEPROVIDER_PRINCIPALID = $env:LIGHTHOUSEPROVIDER_PRINCIPALID | |
$LIGHTHOUSEPROVIDER_MGMTGRPID = $env:LIGHTHOUSEPROVIDER_MGMTGRPID | |
$UNIQUENAME_SUFFIX = $env:UNIQUENAME_SUFFIX | |
$TESTTENANT_BGA1 = $env:TESTTENANT_BGA1 | |
$TESTTENANT_BGA2 = $env:TESTTENANT_BGA2 | |
$PBMMPOLICY_ID = $env:PBMMPOLICY_ID | |
$LOCATIONPOLICY_ID = $env:LOCATIONPOLICY_ID | |
$INITIATIVELOCATION_ID = $env:INITIATIVELOCATION_ID | |
$TESTSEC_RESRC_ID = $env:TESTSEC_RESRC_ID | |
$TESTHEALTHLAW_RESRC_ID = $env:TESTHEALTHLAW_RESRC_ID | |
$ENABLE_MULTICLOUD_PROFILES = $env:ENABLE_MULTICLOUD_PROFILES | |
$configContent = @" | |
{ | |
"keyVaultName": "gsapipe", | |
"resourcegroup": "gsapipe", | |
"region": "CanadaCentral", | |
"storageaccountName": "gsapipe", | |
"logAnalyticsworkspaceName": "gsapipe", | |
"autoMationAccountName": "gsapipe", | |
"FirstBreakGlassAccountUPN": "$TESTTENANT_BGA1", | |
"SecondBreakGlassAccountUPN": "$TESTTENANT_BGA2", | |
"PBMMPolicyID": "$PBMMPOLICY_ID", | |
"AllowedLocationPolicyId": "$LOCATIONPOLICY_ID", | |
"AllowedLocationInitiativeId": "$INITIATIVELOCATION_ID", | |
"DepartmentNumber": "163", | |
"CBSSubscriptionName": "$CBSSUBSCRIPTION_NAME", | |
"securityLAWResourceId": "$TESTSEC_RESRC_ID", | |
"healthLAWResourceId": "$TESTHEALTHLAW_RESRC_ID", | |
"Locale": "en-CA", | |
"lighthouseServiceProviderTenantID": "$LIGHTHOUSEPROVIDER_TENANTID", | |
"lighthousePrincipalDisplayName": "SSC CSPM TEAM", | |
"lighthousePrincipalId": "$LIGHTHOUSEPROVIDER_PRINCIPALID", | |
"lighthouseTargetManagementGroupID": "$LIGHTHOUSEPROVIDER_MGMTGRPID", | |
"subscriptionId": "$TESTSUBSCRIPTION_ID", | |
"SSCReadOnlyServicePrincipalNameAPPID": "00000000-0000-0000-0000-000000000000", | |
"uniqueNameSuffix": "$UNIQUENAME_SUFFIX", | |
"securityRetentionDays": "730", | |
"cloudUsageProfiles": "1,3,5", | |
"enableMultiCloudProfiles": "$ENABLE_MULTICLOUD_PROFILES" | |
} | |
"@ | |
Set-Content -Path $configFilePath -Value $configContent | |
Push-Location -Path setup | |
try { | |
$ErrorActionPreference = 'Stop' | |
remove-gsacentralizedReportingCustomerComponents -Force -configFilePath $configFilePath -verbose | |
Remove-GSACentralizedDefenderCustomerComponents -Force -configFilePath $configFilePath -verbose | |
Remove-GSACoreResources -Force -Wait -configFilePath $configFilePath -verbose | |
} | |
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 }} | |
LIGHTHOUSEPROVIDER_TENANTID: ${{ vars.LIGHTHOUSEPROVIDER_TENANTID }} | |
LIGHTHOUSEPROVIDER_PRINCIPALID: ${{ vars.LIGHTHOUSEPROVIDER_PRINCIPALID }} | |
LIGHTHOUSEPROVIDER_MGMTGRPID: ${{ vars.LIGHTHOUSEPROVIDER_MGMTGRPID }} | |
UNIQUENAME_SUFFIX: ${{ vars.UNIQUENAME_SUFFIX }} | |
TESTTENANT_BGA1: ${{ vars.TESTTENANT_BGA1 }} | |
TESTTENANT_BGA2: ${{ vars.TESTTENANT_BGA2 }} | |
PBMMPOLICY_ID: ${{ vars.PBMMPOLICY_ID }} | |
LOCATIONPOLICY_ID: ${{ vars.LOCATIONPOLICY_ID }} | |
INITIATIVELOCATION_ID: ${{ vars.INITIATIVELOCATION_ID }} | |
TESTSEC_RESRC_ID: ${{ vars.TESTSEC_RESRC_ID }} | |
TESTHEALTHLAW_RESRC_ID: ${{ vars.TESTHEALTHLAW_RESRC_ID }} | |
ENABLE_MULTICLOUD_PROFILES: ${{ vars.TEST_ENABLE_MULTICLOUD_PROFILE }} | |
# - name: ReLogin AZ (due to timeout issue) | |
# uses: azure/login@v2 | |
# with: | |
# client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
# tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
# subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
# enable-AzPSSession: true | |
- name: Deploy Test environment | |
uses: azure/powershell@v2 | |
with: | |
inlineScript: | | |
Set-AzContext -SubscriptionId $env:TESTSUBSCRIPTION_ID | |
$configFilePath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath 'config.json' | |
$CBSSUBSCRIPTION_NAME = $env:CBSSUBSCRIPTION_NAME | |
$TESTSUBSCRIPTION_ID = $env:TESTSUBSCRIPTION_ID | |
$LIGHTHOUSEPROVIDER_TENANTID = $env:LIGHTHOUSEPROVIDER_TENANTID | |
$LIGHTHOUSEPROVIDER_PRINCIPALID = $env:LIGHTHOUSEPROVIDER_PRINCIPALID | |
$LIGHTHOUSEPROVIDER_MGMTGRPID = $env:LIGHTHOUSEPROVIDER_MGMTGRPID | |
$UNIQUENAME_SUFFIX = $env:UNIQUENAME_SUFFIX | |
$TESTTENANT_BGA1 = $env:TESTTENANT_BGA1 | |
$TESTTENANT_BGA2 = $env:TESTTENANT_BGA2 | |
$PBMMPOLICY_ID = $env:PBMMPOLICY_ID | |
$LOCATIONPOLICY_ID = $env:LOCATIONPOLICY_ID | |
$INITIATIVELOCATION_ID = $env:INITIATIVELOCATION_ID | |
$TESTSEC_RESRC_ID = $env:TESTSEC_RESRC_ID | |
$TESTHEALTHLAW_RESRC_ID = $env:TESTHEALTHLAW_RESRC_ID | |
$ENABLE_MULTICLOUD_PROFILES = $env:ENABLE_MULTICLOUD_PROFILES | |
$configContent = @" | |
{ | |
"keyVaultName": "gsapipe", | |
"resourcegroup": "gsapipe", | |
"region": "CanadaCentral", | |
"storageaccountName": "gsapipe", | |
"logAnalyticsworkspaceName": "gsapipe", | |
"autoMationAccountName": "gsapipe", | |
"FirstBreakGlassAccountUPN": "$TESTTENANT_BGA1", | |
"SecondBreakGlassAccountUPN": "$TESTTENANT_BGA2", | |
"PBMMPolicyID": "$PBMMPOLICY_ID", | |
"AllowedLocationPolicyId": "$LOCATIONPOLICY_ID", | |
"AllowedLocationInitiativeId": "$INITIATIVELOCATION_ID", | |
"DepartmentNumber": "163", | |
"CBSSubscriptionName": "$CBSSUBSCRIPTION_NAME", | |
"securityLAWResourceId": "$TESTSEC_RESRC_ID", | |
"healthLAWResourceId": "$TESTHEALTHLAW_RESRC_ID", | |
"Locale": "en-CA", | |
"lighthouseServiceProviderTenantID": "$LIGHTHOUSEPROVIDER_TENANTID", | |
"lighthousePrincipalDisplayName": "SSC CSPM TEAM", | |
"lighthousePrincipalId": "$LIGHTHOUSEPROVIDER_PRINCIPALID", | |
"lighthouseTargetManagementGroupID": "$LIGHTHOUSEPROVIDER_MGMTGRPID", | |
"subscriptionId": "$TESTSUBSCRIPTION_ID", | |
"SSCReadOnlyServicePrincipalNameAPPID": "00000000-0000-0000-0000-000000000000", | |
"uniqueNameSuffix": "$UNIQUENAME_SUFFIX", | |
"securityRetentionDays": "730", | |
"cloudUsageProfiles": "1,3,5", | |
"enableMultiCloudProfiles": "$ENABLE_MULTICLOUD_PROFILES" | |
} | |
"@ | |
Set-Content -Path $configFilePath -Value $configContent | |
# Load tags JSON content into a variable | |
$setupFileRelativePath = "setup/tags.json" | |
$setupFullPath = Join-Path $env:GITHUB_WORKSPACE $setupFileRelativePath | |
$jsonContent = Get-Content -Raw -Path $setupFullPath | ConvertFrom-Json | |
# Add additional required tags for test tenant | |
$jsonContent | Add-Member -Type NoteProperty -Name 'ClientOrganization' -Value 'SSC' | |
$jsonContent | Add-Member -Type NoteProperty -Name 'CostCenter' -Value 'SSC Cloud Operations' | |
$jsonContent | Add-Member -Type NoteProperty -Name 'DataSensitivity' -Value 'PB' | |
$jsonContent | Add-Member -Type NoteProperty -Name 'ProjectContact' -Value 'Amrinder' | |
$jsonContent | Add-Member -Type NoteProperty -Name 'ProjectName' -Value 'ComplianceAsCodeAzure' | |
$jsonContent | Add-Member -Type NoteProperty -Name 'TechnicalContact' -Value 'Amrinder' | |
# Save the modified content back to the tags JSON file | |
$jsonContent | ConvertTo-Json | Set-Content -Path $setupFullPath | |
$storageContext = (Get-AzStorageAccount -ResourceGroupName $env:PIPELINEMODULESTAGING_RGNAME -Name $env:PIPELINEMODULESTAGING_STORAGEACCOUNTNAME).context | |
$modulesStagingURI = $storageContext.BlobEndpoint.ToString() + 'psmodules' | |
$alternatePSModulesURL = $modulesStagingURI | |
Write-Output "alternatePSModulesURL is '$alternatePSModulesURL'" | |
$optionalParams = @{} | |
if ($alternatePSModulesURL) { | |
$optionalParams['alternatePSModulesURL'] = $alternatePSModulesURL | |
} | |
try { | |
$ErrorActionPreference = 'Stop' | |
ipmo ./src/GuardrailsSolutionAcceleratorSetup | |
Deploy-GuardrailsSolutionAccelerator -configFilePath $configFilePath -newComponents CoreComponents, CentralizedCustomerDefenderForCloudSupport, CentralizedCustomerReportingSupport -Yes @optionalParams -verbose | |
} | |
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: | |
PIPELINEMODULESTAGING_RGNAME: ${{ vars.PIPELINEMODULESTAGING_RGNAME }} | |
PIPELINEMODULESTAGING_STORAGEACCOUNTNAME: ${{ vars.PIPELINEMODULESTAGING_SANAME }} | |
TESTSUBSCRIPTION_ID: ${{ vars.TESTSUBSCRIPTION_ID }} | |
CBSSUBSCRIPTION_NAME: ${{ vars.CBSSUBSCRIPTION_NAME }} | |
LIGHTHOUSEPROVIDER_TENANTID: ${{ vars.LIGHTHOUSEPROVIDER_TENANTID }} | |
LIGHTHOUSEPROVIDER_PRINCIPALID: ${{ vars.LIGHTHOUSEPROVIDER_PRINCIPALID }} | |
LIGHTHOUSEPROVIDER_MGMTGRPID: ${{ vars.LIGHTHOUSEPROVIDER_MGMTGRPID }} | |
UNIQUENAME_SUFFIX: ${{ vars.UNIQUENAME_SUFFIX }} | |
TESTTENANT_BGA1: ${{ vars.TESTTENANT_BGA1 }} | |
TESTTENANT_BGA2: ${{ vars.TESTTENANT_BGA2 }} | |
PBMMPOLICY_ID: ${{ vars.PBMMPOLICY_ID }} | |
LOCATIONPOLICY_ID: ${{ vars.LOCATIONPOLICY_ID }} | |
INITIATIVELOCATION_ID: ${{ vars.INITIATIVELOCATION_ID}} | |
TESTSEC_RESRC_ID: ${{ vars.TESTSEC_RESRC_ID }} | |
TESTHEALTHLAW_RESRC_ID: ${{ vars.TESTHEALTHLAW_RESRC_ID }} | |
ENABLE_MULTICLOUD_PROFILES: ${{ vars.TEST_ENABLE_MULTICLOUD_PROFILE }} | |
- name: Check for AA Job Errors | |
uses: azure/powershell@v2 | |
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 }} |