Universal NRP Test #1034
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: Universal NRP Test | |
on: | |
workflow_dispatch: | |
inputs: | |
policy_packages: | |
description: 'List of policy packages to test' | |
required: true | |
default: '[]' | |
pull_request: | |
schedule: | |
- cron: '0 20 * * *' # Every day at 12pm PST (UTC-8) | |
env: | |
storageAccountName: 'osconfigstorage' | |
storageContainerName: 'policypackages' | |
defaultSelfHostedImage: 'ubuntu-22.04' | |
jobs: | |
package: | |
name: Package | |
if: ${{ inputs.policy_packages == '[]' || inputs.policy_packages == '' }} | |
uses: ./.github/workflows/package-build.yml | |
needs: [setup-matrix] | |
with: | |
target: ubuntu-14.04 | |
arch: amd64 | |
artifact: nrp-test | |
package-type: DEB | |
test: true | |
machine-config: true | |
release: ${{ github.event_name == 'pull_request' && false || true }} | |
setup-matrix: | |
name: Setup Matrix | |
runs-on: ubuntu-latest | |
outputs: | |
targets: ${{ steps.matrix.outputs.targets }} | |
custom_download: ${{ steps.matrix.outputs.custom_download }} | |
policy_packages: ${{ steps.matrix.outputs.policy_packages }} | |
default_self_hosted_image: ${{ steps.matrix.outputs.default_self_hosted_image }} | |
steps: | |
- name: Generate Matrix | |
id: matrix | |
env: | |
DEFAULT_POLICYPACKAGES: | | |
[ | |
{ "name": "LinuxSshServerSecurityBaseline", "short-name": "SSH", "resource-count": 20 }, | |
{ "name": "AzureLinuxBaseline", "short-name": "ASB", "resource-count": 168 } | |
] | |
TARGETS: | | |
[ | |
{ "os": "ubuntu", "version": "22.04" } | |
] | |
# { "os": "centos", "version": "8" }, | |
# { "os": "debian", "version": "10" }, | |
# { "os": "debian", "version": "11" }, | |
# { "os": "debian", "version": "12" }, | |
# { "os": "mariner", "version": "2" }, | |
# { "os": "oraclelinux", "version": "8" }, | |
# { "os": "rhel", "version": "7" }, | |
# { "os": "rhel", "version": "8" }, | |
# { "os": "rhel", "version": "9" }, | |
# { "os": "rockylinux", "version": "9" }, | |
# { "os": "sles", "version": "15" }, | |
# { "os": "ubuntu", "version": "20.04" }, | |
# { "os": "almalinux", "version": "9" }, | |
# { "os": "amazonlinux", "version": "2" }, | |
# { "os": "centos", "version": "7" }, | |
# { "os": "oraclelinux", "version": "7" }, | |
# { "os": "sles", "version": "12" }, | |
# { "os": "ubuntu", "version": "16.04" }, | |
# { "os": "ubuntu", "version": "18.04" }, | |
run: | | |
# If no explicit packages defined, use the default packages | |
if [[ '${{ inputs.policy_packages }}' == '[]' || '${{ inputs.policy_packages }}' == '' ]]; then | |
custom_download="false" | |
policy_packages="$DEFAULT_POLICYPACKAGES" | |
else | |
custom_download="true" | |
policy_packages="${{ inputs.policy_packages }}" | |
fi | |
if formated_policy_packages=$(echo $policy_packages | jq -r 'tostring'); then | |
echo "Successfully processed JSON" | |
else | |
echo "Failed to process JSON, attempting to process as raw JSON" | |
formated_policy_packages=$(echo $policy_packages | jq -R -r 'tostring') | |
fi | |
echo $custom_download | |
echo $formated_policy_packages | |
echo targets=$(echo $TARGETS | jq -r 'tostring') >> $GITHUB_OUTPUT | |
echo custom_download=$custom_download >> $GITHUB_OUTPUT | |
echo policy_packages=$formated_policy_packages >> $GITHUB_OUTPUT | |
echo default_self_hosted_image="${{ env.defaultSelfHostedImage }}" >> $GITHUB_OUTPUT | |
custom-download: | |
name: Custom Download | |
if: ${{ needs.setup-matrix.outputs.custom_download == 'true' }} | |
needs: [setup-matrix] | |
runs-on: [self-hosted, 1ES.Pool=ci-pool, '1ES.ImageOverride=${{ needs.setup-matrix.outputs.default_self_hosted_image }}'] | |
steps: | |
- name: Check and Install Az module | |
shell: pwsh | |
run: | | |
Write-Host 'Checking the Az module...' | |
try { | |
Get-InstalledModule Az -AllVersions -ErrorAction Stop | |
Write-Host 'Az module is already installed.' | |
} catch { | |
Write-Host 'Az module is not installed. Trying to install...' | |
Install-Module -Name Az -Repository PSGallery -Force | |
} | |
Write-Host 'Done' | |
- name: Azure login | |
uses: azure/login@v2 | |
with: | |
auth-type: IDENTITY | |
client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
enable-AzPSSession: true | |
- name: Azure PowerShell script | |
uses: azure/powershell@v2 | |
with: | |
azPSVersion: latest | |
inlineScript: | | |
# Download the policy packages from Azure Storage or from a URL | |
$jsonPolicyPackages = '${{ needs.setup-matrix.outputs.policy_packages }}' | |
# ConvertFrom-Json operates on unescaped JSON strings, so we need to remove the leading/trailing quotes and backslashes | |
$jsonPolicyPackages = $jsonPolicyPackages.Substring(1, $jsonPolicyPackages.Length - 2) -replace '\\', '' | |
$policyPackages = ConvertFrom-Json $jsonPolicyPackages | |
foreach ($package in $policyPackages) { | |
$policyPackagUrl=$package.'policy-package-url' | |
$storageURIPrefix="storage://" | |
if ($policyPackagUrl.StartsWith($storageURIPrefix)) { | |
$storagePath=$policyPackagUrl.Substring($storageURIPrefix.Length) | |
Write-Host "Downloading $storagePath from Azure Storage" | |
$storageContext = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount | |
Get-AzStorageBlobContent -Container $env:storageContainerName -Blob $storagePath -Context $storageContext -Destination $storagePath | |
} else { | |
Write-Host "Downloading from url \"$policyPackagUrl\"" | |
Invoke-WebRequest -Uri $policyPackagUrl -OutFile PolicyPackage.zip | |
} | |
} | |
- uses: actions/upload-artifact@v4 | |
with: | |
name: nrp-test | |
path: '*.zip' | |
mc-test: | |
name: MC Test | |
if: ${{ always() }} | |
needs: [setup-matrix, package, custom-download] | |
runs-on: [self-hosted, 1ES.Pool=ci-pool, '1ES.ImageOverride=${{ matrix.target.os }}-${{ matrix.target.version }}'] | |
strategy: | |
fail-fast: false | |
matrix: | |
target: ${{ fromJSON(needs.setup-matrix.outputs.targets) }} | |
arch: [amd64] | |
mode: [Audit, Remediate] | |
policy-package: ${{ fromJSON(needs.setup-matrix.outputs.policy_packages) }} | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: actions/download-artifact@v4 | |
id: download | |
with: | |
name: nrp-test | |
- name: Normalize variables | |
id: normalize | |
run: | | |
echo name="${{ matrix.target.os }}-${{ matrix.target.version }}_${{ matrix.policy-package.short-name }}-${{ matrix.mode }}" >> $GITHUB_OUTPUT | |
echo dir="${{ steps.download.outputs.download-path }}" >> $GITHUB_OUTPUT | |
echo path="${{ steps.download.outputs.download-path }}/${{ matrix.policy-package.name }}.zip" >> $GITHUB_OUTPUT | |
- name: Fix policy package names | |
if: ${{ needs.setup-matrix.outputs.custom_download == 'true' }} | |
working-directory: ${{ steps.normalize.outputs.PolicyPackageDir }} | |
shell: pwsh | |
run: | | |
$name="${{ matrix.policy-package.name }}" | |
Get-ChildItem -Path $name*.zip -File | Select-Object -First 1 { | |
Write-Host "Renaming $($_.Name) to $name.zip" | |
Rename-Item -Path $_.Name -NewName "$name.zip" | |
} | |
- name: Run Guest Configuration Test | |
working-directory: ${{ steps.normalize.outputs.PolicyPackageDir }} | |
run: | | |
script="./universalNRPTest.ps1" | |
cat >$script <<EOL | |
Install-Module -Name GuestConfiguration -Force | |
Install-Module Pester -Force -SkipPublisherCheck | |
Import-Module Pester -Passthru | |
\$params = @{ | |
PolicyPackage = '${{ steps.normalize.outputs.path }}' | |
SkipRemediation = if ('${{ matrix.mode }}' -eq 'Audit') { \$true } else { \$false } | |
ResourceCount = ${{ matrix.policy-package.resource-count }} | |
} | |
\$container = New-PesterContainer -Path ./src/tests/universal-nrp-e2e/UniversalNRP.Tests.ps1 -Data \$params | |
\$pesterConfig = [PesterConfiguration]@{ | |
Run = @{ | |
Exit = \$true | |
Container = \$container | |
} | |
Output = @{ | |
Verbosity = 'Detailed' | |
} | |
TestResult = @{ | |
Enabled = \$true | |
OutputFormat = 'JUnitXml' | |
OutputPath = '${{ steps.normalize.outputs.name }}-testResults.xml' | |
} | |
Should = @{ | |
ErrorAction = 'Continue' | |
} | |
}; | |
Invoke-Pester -Configuration \$pesterConfig | |
EOL | |
sudo LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/omi/lib/ pwsh -Command $script | |
stat *testResults.xml | |
- name: Stage OSConfig Logs | |
if: success() || failure() | |
run: | | |
mkdir osconfig-logs | |
stat /var/log/osconfig_nrp.log | |
cp -r /var/log/osconfig* osconfig-logs/ | |
- uses: actions/upload-artifact@v4 | |
if: success() || failure() | |
with: | |
name: ${{ steps.normalize.outputs.name }}_report | |
path: '${{ steps.normalize.outputs.dir }}/*testResults.xml' | |
- uses: actions/upload-artifact@v4 | |
if: success() || failure() | |
with: | |
name: ${{ steps.normalize.outputs.name }}_logs | |
path: osconfig-logs/osconfig* | |
module-test: | |
name: Module Test | |
# Module test requires the package artifact as it also includes the modules to be tested in the artifact | |
if: ${{ needs.setup-matrix.outputs.custom_download == 'false' }} | |
needs: [setup-matrix, package] | |
runs-on: [self-hosted, 1ES.Pool=ci-pool, '1ES.ImageOverride=${{ matrix.target.os }}-${{ matrix.target.version }}'] | |
strategy: | |
fail-fast: false | |
matrix: | |
target: ${{ fromJSON(needs.setup-matrix.outputs.targets) }} | |
arch: [amd64] | |
steps: | |
- uses: actions/checkout@v4 | |
- run: ldd --version | |
- uses: actions/download-artifact@v4 | |
id: download | |
with: | |
name: nrp-test | |
- name: Create osconfig.json | |
run: | | |
sudo mkdir -p /etc/osconfig | |
sudo cp -r ${{ steps.download.outputs.download-path }}/modules/test/osconfig.json /etc/osconfig/osconfig.json | |
- name: Run moduletest | |
working-directory: ${{ steps.download.outputs.download-path }}/modules/test | |
run: | | |
sudo chmod +x ./moduletest | |
result=0 | |
recipe="./recipes/SecurityBaselineTests.json" | |
name=$(basename $recipe | tr '[:upper:]' '[:lower:]' | sed 's/\.[^.]*$//' | sed 's/\(test\|tests\)$//') | |
echo -n "testing $name ... " | |
if output=$(sudo ./moduletest $recipe --bin ../bin); then | |
echo passed | |
else | |
echo failed | |
result=1 | |
echo "::warning file=$name.log::Error(s) in module-test for '$name'" | |
fi | |
echo "$output" | |
echo "$output" > ../../$name.log | |
exit $result | |
- uses: actions/upload-artifact@v4 | |
if: success() || failure() | |
with: | |
name: ${{ matrix.target.os }}-${{ matrix.target.version }}_logs | |
path: '*.log' | |
# See for more details: https://github.com/marketplace/actions/publish-test-results | |
report: | |
name: Report | |
needs: mc-test | |
runs-on: ubuntu-latest | |
permissions: | |
checks: write | |
pull-requests: write | |
if: always() | |
steps: | |
- name: Download Test Report Artifacts | |
uses: actions/download-artifact@v4 | |
with: | |
path: universal-nrp-test | |
pattern: '*_report' | |
merge-multiple: true | |
- name: Publish Test Results | |
uses: EnricoMi/publish-unit-test-result-action@v2 | |
with: | |
files: 'universal-nrp-test/*testResults.xml' |