зеркало из https://github.com/microsoft/AL-Go.git
Federated credentials (#1094)
If the AppSourceContext doesn't contain a clientSecret, then we assume that federated credentials have been assigned and are available, so we will try to get an ID_TOKEN and use clientAssertion instead Same with other Auth Context's - if they contain a clientId and no clientSecret, we will use clientAssertion instead. Azure_Credentials also supports app registrations or managed identities with federated credentials if it contains a clientId without a clientSecret Here are the supported formats for the various auth contexts: ## STORAGECONTEXT (for deliver to storage) **Managed Identity/Federated credential** `{"storageAccountName":"storageaccountname","clientId":"08b6d80c-68cf-48f9-a5ff-b054326e2ec3","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` **App Registration/Federated credential** `{"storageAccountName":"storageaccountname","clientId":"a26651f5-0e90-473c-b4f9-e96119aac8b8","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` **App Registration/Client Secret** `{"storageAccountName":"storageaccountname","clientId":"a26651f5-0e90-473c-b4f9-e96119aac8b8","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` **storageAccountName/sastoken** `{"storageAccountName":"storageaccountname","sastoken":"sv=2022-11-02&ss=b&srt=sco&sp=rwdlaciytf&se=2024-08-06T20:22:08Z&st=2024-04-06T12:22:08Z&spr=https&sig=IZyIf5xxxxxxxxxxxxxxb5I%3D","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` **storageAccountName/storageAccountKey** `{"storageAccountName":"storageaccountname","storageAccountKey":"JHFZErCyxxxxxxxxxxxxxxxxXQ==","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` ## AZURE_CREDENTIALS (connection to Azure for secrets and signing) **Access Configuration = Azure role-based access control** ``` Access Control: Action Role Read secrets Key Vault Secrets User Sign Apps Key Vault Crypto User + Key Vault Certificate User ``` **Access Configuration = Vault Access Policy** ``` Action Permissions: Read secrets Secret permissions: Get, List Sign apps Cryptographic Operations: Sign + Certificate permissions: Get ``` **Managed Identity/Federated credential** `{"keyVaultName":"BuildVariables","clientId":"55ce849b-c99d-484c-8999-df9f8df958bd","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47"}` **App Registration/Federated credential** `{"keyVaultName":"BuildVariables","clientId":"a26651f5-0e90-473c-b4f9-e96119aac8b8","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47"}` **App Registration/Client Secret** `{"keyVaultName":"BuildVariables","clientId":"a26651f5-0e90-473c-b4f9-e96119aac8b8","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47"}` ## APPSOURCECONTEXT (for deliver to AppSource) **Managed identity not possible - as this is not an Azure resource** **App Registration/Federated credential** `{"clientId":"a26651f5-0e90-473c-b4f9-e96119aac8b8","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","Scopes":"https://api.partner.microsoft.com/.default"}` **App Registration/Client Secret** `{"clientId":"a26651f5-0e90-473c-b4f9-e96119aac8b8","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"72f988bf-86f1-41af-91ab-2d7cd011db47","Scopes":"https://api.partner.microsoft.com/.default"}` ## AUTHCONTEXT (for deploy to Business Central) **Managed identity not possible - as this is not an Azure resource** **Impersonation/RefreshToken** `{"TenantID":"69cb4a05-4ea8-482d-9f33-10fb5cf7db05","Scopes":"https://api.businesscentral.dynamics.com/","RefreshToken":"0.AUUAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_s6Eo4YOI","ClientID":"1950a258-227b-4e31-a9cf-717495945fc2"}` **App Registration/Federated credential** `{"TenantID":"69cb4a05-4ea8-482d-9f33-10fb5cf7db05","Scopes":"https://api.businesscentral.dynamics.com/","ClientID":"a26651f5-0e90-473c-b4f9-e96119aac8b8"}` **App Registration/Client Secret** `{"TenantID":"69cb4a05-4ea8-482d-9f33-10fb5cf7db05","Scopes":"https://api.businesscentral.dynamics.com/","ClientID":"a26651f5-0e90-473c-b4f9-e96119aac8b8","ClientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge"}` TODOs - [x] Add documentation in codesigning.md (rbac) - [x] Add documentation for appSourceContext federated credentials - [x] Add documentation for other auth contexts - [x] Add aka.ms/algosecrets#secretname - [x] Add End 2 End test testing all these auth methods This PR also switches to always use the Az PowerShell module (instead of the deprecated AzureRM PowerShell module installed on GitHub Hosted Windows runners) Fixes #947 --------- Co-authored-by: freddydk <freddydk@users.noreply.github.com> Co-authored-by: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> Co-authored-by: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com>
This commit is contained in:
Родитель
7a66ed6da4
Коммит
9aa6e6ff03
|
@ -182,7 +182,7 @@ jobs:
|
|||
Write-Host "scenarios=$scenariosJson"
|
||||
|
||||
Scenario:
|
||||
runs-on: [ ubuntu-latest ]
|
||||
runs-on: [ windows-latest ]
|
||||
needs: [ Check, SetupRepositories, Analyze ]
|
||||
if: github.event.inputs.runScenarios == 'true'
|
||||
strategy: ${{ fromJson(needs.Analyze.outputs.scenarios) }}
|
||||
|
|
|
@ -6,7 +6,7 @@ repos:
|
|||
rev: 0.7.17
|
||||
hooks:
|
||||
- id: mdformat
|
||||
args: [--end-of-line=crlf]
|
||||
args: [--end-of-line=keep]
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
|
|
|
@ -1269,21 +1269,25 @@ function GetProjectFolders {
|
|||
$projectFolders
|
||||
}
|
||||
|
||||
function installModules {
|
||||
function InstallModule {
|
||||
Param(
|
||||
[String[]] $modules
|
||||
[String] $name,
|
||||
[System.Version] $minimumVersion = $null
|
||||
)
|
||||
|
||||
$modules | ForEach-Object {
|
||||
if (-not (get-installedmodule -Name $_ -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Installing module $_"
|
||||
Install-Module $_ -Force | Out-Null
|
||||
}
|
||||
if ($null -eq $minimumVersion) {
|
||||
$minimumVersion = [System.Version](GetPackageVersion -packageName $name)
|
||||
}
|
||||
$modules | ForEach-Object {
|
||||
Write-Host "Importing module $_"
|
||||
Import-Module $_ -DisableNameChecking -WarningAction SilentlyContinue | Out-Null
|
||||
$module = Get-Module -name $name -ListAvailable | Select-Object -First 1
|
||||
if ($module -and $module.Version -ge $minimumVersion) {
|
||||
Write-Host "Module $name is available in version $($module.Version)"
|
||||
}
|
||||
else {
|
||||
Write-Host "Installing module $name (minimum version $minimumVersion)"
|
||||
Install-Module -Name $name -MinimumVersion "$minimumVersion" -Force | Out-Null
|
||||
}
|
||||
Write-Host "Importing module $name (minimum version $minimumVersion)"
|
||||
Import-Module -Name $name -MinimumVersion $minimumVersion -DisableNameChecking -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
|
||||
function CloneIntoNewFolder {
|
||||
|
@ -1329,7 +1333,7 @@ function CommitFromNewFolder {
|
|||
Param(
|
||||
[string] $serverUrl,
|
||||
[string] $commitMessage,
|
||||
[string] $body = '',
|
||||
[string] $body = $commitMessage,
|
||||
[string] $branch
|
||||
)
|
||||
|
||||
|
@ -1664,7 +1668,7 @@ function CreateDevEnv {
|
|||
|
||||
if (($settings.keyVaultName) -and -not ($bcAuthContext)) {
|
||||
Write-Host "Reading Key Vault $($settings.keyVaultName)"
|
||||
installModules -modules @('Az.KeyVault')
|
||||
InstallAzModuleIfNeeded -name 'Az.KeyVault'
|
||||
|
||||
if ($kind -eq "local") {
|
||||
$LicenseFileSecret = Get-AzKeyVaultSecret -VaultName $settings.keyVaultName -Name $settings.licenseFileUrlSecretName
|
||||
|
@ -2357,14 +2361,80 @@ function GetProjectsFromRepository {
|
|||
return @(GetMatchingProjects -projects $projects -selectProjects $selectProjects)
|
||||
}
|
||||
|
||||
function Get-PackageVersion($PackageName) {
|
||||
function GetPackageVersion($packageName) {
|
||||
$alGoPackages = Get-Content -Path "$PSScriptRoot\Packages.json" | ConvertFrom-Json
|
||||
|
||||
# Check if the package is in the list of packages
|
||||
if ($alGoPackages.PSobject.Properties.name -match $PackageName) {
|
||||
return $alGoPackages.$PackageName
|
||||
if ($alGoPackages.PSobject.Properties.name -eq $PackageName) {
|
||||
return $alGoPackages."$PackageName"
|
||||
}
|
||||
else {
|
||||
throw "Package $PackageName is not in the list of packages"
|
||||
}
|
||||
}
|
||||
|
||||
function InstallAzModuleIfNeeded {
|
||||
Param(
|
||||
[string] $name,
|
||||
[System.version] $minimumVersion = $null
|
||||
)
|
||||
|
||||
if ($null -eq $minimumVersion) {
|
||||
$minimumVersion = [System.Version](GetPackageVersion -packageName $name)
|
||||
}
|
||||
$azModule = Get-Module -Name $name
|
||||
if ($azModule -and $azModule.Version -ge $minimumVersion) {
|
||||
# Already installed
|
||||
return
|
||||
}
|
||||
# GitHub hosted Linux runners have AZ PowerShell module saved in /usr/share/powershell/Modules/Az.*
|
||||
if ($isWindows) {
|
||||
# GitHub hosted Windows Runners have AzureRm PowerShell modules installed (deprecated)
|
||||
# GitHub hosted Windows Runners have AZ PowerShell module saved in C:\Modules\az_*
|
||||
# Remove AzureRm modules from PSModulePath and add AZ modules
|
||||
if (Test-Path 'C:\Modules\az_*') {
|
||||
$azModulesPath = Get-ChildItem 'C:\Modules\az_*' | Where-Object { $_.PSIsContainer }
|
||||
if ($azModulesPath) {
|
||||
Write-Host "Adding AZ module path: $($azModulesPath.FullName)"
|
||||
$ENV:PSModulePath = "$($azModulesPath.FullName);$(("$ENV:PSModulePath".Split(';') | Where-Object { $_ -notlike 'C:\\Modules\Azure*' }) -join ';')"
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallModule -name $name -minimumVersion $minimumVersion
|
||||
}
|
||||
|
||||
function ConnectAz {
|
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'GitHub Secrets come in as plain text')]
|
||||
param(
|
||||
[PsCustomObject] $azureCredentials
|
||||
)
|
||||
try {
|
||||
Clear-AzContext -Scope Process
|
||||
Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue
|
||||
if ($azureCredentials.PSObject.Properties.Name -eq 'ClientSecret' -and $azureCredentials.ClientSecret) {
|
||||
Write-Host "Connecting to Azure using clientId and clientSecret."
|
||||
$credential = New-Object pscredential -ArgumentList $azureCredentials.ClientId, (ConvertTo-SecureString -string $azureCredentials.ClientSecret -AsPlainText -Force)
|
||||
Connect-AzAccount -ServicePrincipal -Tenant $azureCredentials.TenantId -Credential $credential -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
else {
|
||||
try {
|
||||
Write-Host "Query federated token"
|
||||
$result = Invoke-RestMethod -Method GET -UseBasicParsing -Headers @{ "Authorization" = "bearer $ENV:ACTIONS_ID_TOKEN_REQUEST_TOKEN"; "Accept" = "application/vnd.github+json" } -Uri "$ENV:ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange"
|
||||
}
|
||||
catch {
|
||||
throw "Unable to get federated token, maybe id_token: write permissions are missing. Error was $($_.Exception.Message)"
|
||||
}
|
||||
Write-Host "Connecting to Azure using clientId and federated token."
|
||||
Connect-AzAccount -ApplicationId $azureCredentials.ClientId -Tenant $azureCredentials.TenantId -FederatedToken $result.value -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
if ($azureCredentials.PSObject.Properties.Name -eq 'SubscriptionId' -and $azureCredentials.SubscriptionId) {
|
||||
Write-Host "Selecting subscription $($azureCredentials.SubscriptionId)"
|
||||
Set-AzContext -SubscriptionId $azureCredentials.SubscriptionId -Tenant $azureCredentials.TenantId -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
$script:keyvaultConnectionExists = $true
|
||||
Write-Host "Successfully connected to Azure"
|
||||
}
|
||||
catch {
|
||||
throw "Error trying to authenticate to Azure. Error was $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,31 +20,49 @@
|
|||
[bool] $goLive
|
||||
)
|
||||
|
||||
$telemetryScope = $null
|
||||
function ConnectAzStorageAccount {
|
||||
Param(
|
||||
[PSCustomObject] $storageAccountCredentials
|
||||
)
|
||||
|
||||
function EnsureAzStorageModule() {
|
||||
if (get-command New-AzStorageContext -ErrorAction SilentlyContinue) {
|
||||
Write-Host "Using Az.Storage PowerShell module"
|
||||
$azStorageContext = $null
|
||||
if ($storageAccountCredentials.PSObject.Properties.Name -eq 'sastoken') {
|
||||
try {
|
||||
Write-Host "Creating AzStorageContext based on StorageAccountName and sastoken"
|
||||
$azStorageContext = New-AzStorageContext -StorageAccountName $storageAccountCredentials.StorageAccountName -SasToken $storageAccountCredentials.sastoken
|
||||
}
|
||||
catch {
|
||||
throw "Unable to create AzStorageContext based on StorageAccountName and sastoken. Error was: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
elseif ($storageAccountCredentials.PSObject.Properties.Name -eq 'StorageAccountKey') {
|
||||
try {
|
||||
Write-Host "Creating AzStorageContext based on StorageAccountName and StorageAccountKey"
|
||||
$azStorageContext = New-AzStorageContext -StorageAccountName $storageAccountCredentials.StorageAccountName -StorageAccountKey $storageAccountCredentials.StorageAccountKey
|
||||
}
|
||||
catch {
|
||||
throw "Unable to create AzStorageContext based on StorageAccountName and StorageAccountKey. Error was: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
elseif (($storageAccountCredentials.PSObject.Properties.Name -eq 'clientID') -and ($storageAccountCredentials.PSObject.Properties.Name -eq 'tenantID')) {
|
||||
try {
|
||||
InstallAzModuleIfNeeded -name 'Az.Accounts'
|
||||
ConnectAz -azureCredentials $storageAccountCredentials
|
||||
Write-Host "Creating AzStorageContext based on StorageAccountName and managed identity/app registration"
|
||||
$azStorageContext = New-AzStorageContext -StorageAccountName $storageAccountCredentials.StorageAccountName -UseConnectedAccount
|
||||
}
|
||||
catch {
|
||||
throw "Unable to create AzStorageContext based on StorageAccountName and managed identity. Error was: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$azureStorageModule = Get-Module -name 'Azure.Storage' -ListAvailable | Select-Object -First 1
|
||||
if ($azureStorageModule) {
|
||||
Write-Host "Azure.Storage Module is available in version $($azureStorageModule.Version)"
|
||||
Write-Host "Using Azure.Storage version $($azureStorageModule.Version)"
|
||||
Import-Module 'Azure.Storage' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null
|
||||
Set-Alias -Name New-AzStorageContext -Value New-AzureStorageContext -Scope Script
|
||||
Set-Alias -Name Get-AzStorageContainer -Value Get-AzureStorageContainer -Scope Script
|
||||
Set-Alias -Name New-AzStorageContainer -Value New-AzureStorageContainer -Scope Script
|
||||
Set-Alias -Name Set-AzStorageBlobContent -Value Set-AzureStorageBlobContent -Scope Script
|
||||
}
|
||||
else {
|
||||
Write-Host "Installing and importing Az.Storage."
|
||||
Install-Module 'Az.Storage' -Force
|
||||
Import-Module 'Az.Storage' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
throw "Insufficient information in StorageContext secret. See https://aka.ms/algosettings#storagecontext for details"
|
||||
}
|
||||
return $azStorageContext
|
||||
}
|
||||
|
||||
$telemetryScope = $null
|
||||
|
||||
try {
|
||||
. (Join-Path -Path $PSScriptRoot -ChildPath "../AL-Go-Helper.ps1" -Resolve)
|
||||
DownloadAndImportBcContainerHelper
|
||||
|
@ -315,35 +333,17 @@ try {
|
|||
Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package
|
||||
}
|
||||
elseif ($deliveryTarget -eq "Storage") {
|
||||
EnsureAzStorageModule
|
||||
InstallAzModuleIfNeeded -name 'Az.Storage'
|
||||
try {
|
||||
$storageAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.storageContext)) | ConvertFrom-Json | ConvertTo-HashTable
|
||||
# Check that containerName and blobName are present
|
||||
$storageAccount.containerName | Out-Null
|
||||
$storageAccount.blobName | Out-Null
|
||||
$storageAccountCredentials = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.storageContext)) | ConvertFrom-Json
|
||||
$storageAccountCredentials.StorageAccountName | Out-Null
|
||||
$storageContainerName = $storageAccountCredentials.ContainerName.ToLowerInvariant().replace('{project}',$projectName).replace('{branch}',$refname).ToLowerInvariant()
|
||||
$storageBlobName = $storageAccountCredentials.BlobName.ToLowerInvariant()
|
||||
}
|
||||
catch {
|
||||
throw "StorageContext secret is malformed. Needs to be formatted as Json, containing StorageAccountName, containerName, blobName and sastoken or storageAccountKey.`nError was: $($_.Exception.Message)"
|
||||
throw "StorageContext secret is malformed. Needs to be formatted as Json, containing StorageAccountName, containerName, blobName.`nError was: $($_.Exception.Message)"
|
||||
}
|
||||
if ($storageAccount.Keys -contains 'sastoken') {
|
||||
try {
|
||||
$azStorageContext = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -SasToken $storageAccount.sastoken
|
||||
}
|
||||
catch {
|
||||
throw "Unable to create AzStorageContext based on StorageAccountName and sastoken.`nError was: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$azStorageContext = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -StorageAccountKey $storageAccount.StorageAccountKey
|
||||
}
|
||||
catch {
|
||||
throw "Unable to create AzStorageContext based on StorageAccountName and StorageAccountKey.`nError was: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$storageContainerName = $storageAccount.ContainerName.ToLowerInvariant().replace('{project}',$projectName).replace('{branch}',$refname).ToLowerInvariant()
|
||||
$storageBlobName = $storageAccount.BlobName.ToLowerInvariant()
|
||||
$azStorageContext = ConnectAzStorageAccount -storageAccountCredentials $storageAccountCredentials
|
||||
Write-Host "Storage Container Name is $storageContainerName"
|
||||
Write-Host "Storage Blob Name is $storageBlobName"
|
||||
|
||||
|
@ -360,7 +360,7 @@ try {
|
|||
New-AzStorageContainer -Context $azStorageContext -Name $storageContainerName | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "Delivering to $storageContainerName in $($storageAccount.StorageAccountName)"
|
||||
Write-Host "Delivering to $storageContainerName in $($storageAccountCredentials.StorageAccountName)"
|
||||
$atypes.Split(',') | ForEach-Object {
|
||||
$atype = $_
|
||||
Write-Host "Looking for: $project-$refname-$atype-*.*.*.*"
|
||||
|
@ -415,7 +415,8 @@ try {
|
|||
# if type is Release, we only get here with the projects that needs to be delivered to AppSource
|
||||
# if type is CD, we get here for all projects, but should only deliver to AppSource if AppSourceContinuousDelivery is set to true
|
||||
if ($type -eq 'Release' -or $projectSettings.deliverToAppSource.continuousDelivery) {
|
||||
EnsureAzStorageModule
|
||||
# AppSource submission requires the Az.Storage module
|
||||
InstallAzModuleIfNeeded -name 'Az.Storage'
|
||||
$appSourceContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.appSourceContext)) | ConvertFrom-Json | ConvertTo-HashTable
|
||||
if (!$appSourceContext) {
|
||||
throw "appSourceContext secret is missing"
|
||||
|
|
|
@ -20,16 +20,6 @@ $deploymentSettings = $deploymentEnvironments."$environmentName"
|
|||
$envName = $environmentName.Split(' ')[0]
|
||||
$secrets = $env:Secrets | ConvertFrom-Json
|
||||
|
||||
# Check obsolete secrets
|
||||
"$($envName)-EnvironmentName","$($envName)_EnvironmentName","EnvironmentName" | ForEach-Object {
|
||||
if ($secrets."$_") {
|
||||
throw "The secret $_ is obsolete and should be replaced by using the EnvironmentName property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead"
|
||||
}
|
||||
}
|
||||
if ($secrets.Projects) {
|
||||
throw "The secret Projects is obsolete and should be replaced by using the Projects property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead"
|
||||
}
|
||||
|
||||
$authContext = $null
|
||||
foreach($secretName in "$($envName)-AuthContext","$($envName)_AuthContext","AuthContext") {
|
||||
if ($secrets."$secretName") {
|
||||
|
@ -110,7 +100,7 @@ else {
|
|||
|
||||
try {
|
||||
$sandboxEnvironment = ($response.environmentType -eq 1)
|
||||
if ($sandboxEnvironment -and !($bcAuthContext.ClientSecret)) {
|
||||
if ($sandboxEnvironment -and !($bcAuthContext.ClientSecret -or $bcAuthContext.ClientAssertion)) {
|
||||
# Sandbox and not S2S -> use dev endpoint (Publish-BcContainerApp)
|
||||
$parameters = @{
|
||||
"bcAuthContext" = $bcAuthContext
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
function IncludeBranch([string] $deliveryTarget) {
|
||||
$settingsName = "DeliverTo$deliveryTarget"
|
||||
if ($settings.Contains($settingsName) -and $settings."$settingsName".Contains('Branches')) {
|
||||
Write-Host "- Branches defined: $($settings."$settingsName".Branches -join ', ') - "
|
||||
Write-Host "- Branches defined: $($settings."$settingsName".Branches -join ', ')"
|
||||
return ($null -ne ($settings."$settingsName".Branches | Where-Object { $ENV:GITHUB_REF_NAME -like $_ }))
|
||||
}
|
||||
else {
|
||||
|
@ -18,7 +18,7 @@ function IncludeBranch([string] $deliveryTarget) {
|
|||
}
|
||||
|
||||
function IncludeDeliveryTarget([string] $deliveryTarget) {
|
||||
Write-Host "DeliveryTarget $_ - "
|
||||
Write-Host "DeliveryTarget $_"
|
||||
# DeliveryTarget Context Secret needs to be specified for a delivery target to be included
|
||||
$contextName = "$($_)Context"
|
||||
$secrets = $env:Secrets | ConvertFrom-Json
|
||||
|
@ -38,7 +38,7 @@ if ($settings.type -eq "AppSource App") {
|
|||
($projectsJson | ConvertFrom-Json) | ForEach-Object {
|
||||
$projectSettings = ReadSettings -project $_
|
||||
if ($projectSettings.deliverToAppSource.ContinuousDelivery -or ($projectSettings.Contains('AppSourceContinuousDelivery') -and $projectSettings.AppSourceContinuousDelivery)) {
|
||||
Write-Host "Project $_ is setup for Continuous Delivery"
|
||||
Write-Host "Project $_ is setup for Continuous Delivery to AppSource"
|
||||
$deliveryTargets += @("AppSource")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
{
|
||||
"sign": "0.9.1-beta.24123.2"
|
||||
"sign": "0.9.1-beta.24123.2",
|
||||
"Az.Accounts": "2.15.1",
|
||||
"Az.Storage": "6.1.1",
|
||||
"Az.KeyVault": "5.2.0"
|
||||
}
|
||||
|
|
|
@ -100,6 +100,17 @@ try {
|
|||
MaskValue -key "$($secretName).$($keyName)" -value "$($json."$keyName")"
|
||||
}
|
||||
}
|
||||
if ($json.ContainsKey('clientID') -and !($json.ContainsKey('clientSecret') -or $json.ContainsKey('refreshToken'))) {
|
||||
try {
|
||||
Write-Host "Query federated token"
|
||||
$result = Invoke-RestMethod -Method GET -UseBasicParsing -Headers @{ "Authorization" = "bearer $ENV:ACTIONS_ID_TOKEN_REQUEST_TOKEN"; "Accept" = "application/vnd.github+json" } -Uri "$ENV:ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange"
|
||||
$json += @{ "clientAssertion" = $result.value }
|
||||
$secretValue = $json | ConvertTo-Json -Compress
|
||||
}
|
||||
catch {
|
||||
throw "$SecretName doesn't contain any ClientSecret and AL-Go is unable to acquire an ID_TOKEN. Error was $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
$base64value = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($secretValue))
|
||||
$outSecrets += @{ "$secretsProperty" = $base64value }
|
||||
|
|
|
@ -5,13 +5,10 @@ Param(
|
|||
|
||||
$script:gitHubSecrets = $_gitHubSecrets | ConvertFrom-Json
|
||||
$script:keyvaultConnectionExists = $false
|
||||
$script:azureRm210 = $false
|
||||
$script:isKeyvaultSet = $script:gitHubSecrets.PSObject.Properties.Name -eq "AZURE_CREDENTIALS"
|
||||
$script:escchars = @(' ','!','\"','#','$','%','\u0026','\u0027','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','\u003c','=','\u003e','?','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',[char]96,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','{','|','}','~')
|
||||
|
||||
function IsKeyVaultSet {
|
||||
return $script:isKeyvaultSet
|
||||
}
|
||||
. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)
|
||||
|
||||
function MaskValue {
|
||||
Param(
|
||||
|
@ -78,12 +75,12 @@ function GetKeyVaultCredentials {
|
|||
}
|
||||
try {
|
||||
$creds = $jsonStr | ConvertFrom-Json
|
||||
# Mask ClientSecret
|
||||
MaskValue -key 'clientSecret' -value $creds.ClientSecret
|
||||
$creds.ClientSecret = ConvertTo-SecureString $creds.ClientSecret -AsPlainText -Force
|
||||
if ($creds.PSObject.Properties.Name -eq 'ClientSecret' -and $creds.ClientSecret) {
|
||||
# Mask ClientSecret
|
||||
MaskValue -key 'ClientSecret' -value $creds.ClientSecret
|
||||
}
|
||||
# Check thet $creds contains the needed properties
|
||||
$creds.ClientId | Out-Null
|
||||
$creds.subscriptionId | Out-Null
|
||||
$creds.TenantId | Out-Null
|
||||
}
|
||||
catch {
|
||||
|
@ -108,75 +105,6 @@ function GetKeyVaultCredentials {
|
|||
return $creds
|
||||
}
|
||||
|
||||
function InstallKeyVaultModuleIfNeeded {
|
||||
if ($isWindows -and (Test-Path 'C:\Modules\az_*')) {
|
||||
$azModulesPath = Get-ChildItem 'C:\Modules\az_*' | Where-Object { $_.PSIsContainer }
|
||||
if ($azModulesPath) {
|
||||
Write-Host $azModulesPath.FullName
|
||||
$ENV:PSModulePath = "$($azModulesPath.FullName);$(("$ENV:PSModulePath".Split(';') | Where-Object { $_ -notlike 'C:\\Modules\Azure*' }) -join ';')"
|
||||
}
|
||||
}
|
||||
|
||||
$azKeyVaultModule = Get-Module -name 'Az.KeyVault' -ListAvailable | Select-Object -First 1
|
||||
if ($azKeyVaultModule) {
|
||||
Write-Host "Az.KeyVault Module is available in version $($azKeyVaultModule.Version)"
|
||||
Write-Host "Using Az.KeyVault version $($azKeyVaultModule.Version)"
|
||||
Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
else {
|
||||
$AzKeyVaultModule = Get-InstalledModule -Name 'Az.KeyVault' -ErrorAction SilentlyContinue
|
||||
if ($AzKeyVaultModule) {
|
||||
Write-Host "Az.KeyVault version $($AzKeyVaultModule.Version) is installed"
|
||||
Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue
|
||||
}
|
||||
else {
|
||||
$azureRmKeyVaultModule = Get-Module -name 'AzureRm.KeyVault' -ListAvailable | Select-Object -First 1
|
||||
if ($azureRmKeyVaultModule) { Write-Host "AzureRm.KeyVault Module is available in version $($azureRmKeyVaultModule.Version)" }
|
||||
$azureRmProfileModule = Get-Module -name 'AzureRm.Profile' -ListAvailable | Select-Object -First 1
|
||||
if ($azureRmProfileModule) { Write-Host "AzureRm.Profile Module is available in version $($azureRmProfileModule.Version)" }
|
||||
if ($azureRmKeyVaultModule -and $azureRmProfileModule -and "$($azureRmKeyVaultModule.Version)" -eq "2.1.0" -and "$($azureRmProfileModule.Version)" -eq "2.1.0") {
|
||||
Write-Host "Using AzureRM version 2.1.0"
|
||||
$script:azureRm210 = $true
|
||||
$azureRmKeyVaultModule | Import-Module -WarningAction SilentlyContinue
|
||||
$azureRmProfileModule | Import-Module -WarningAction SilentlyContinue
|
||||
Disable-AzureRmDataCollection -WarningAction SilentlyContinue
|
||||
}
|
||||
else {
|
||||
Write-Host "Installing and importing Az.KeyVault."
|
||||
Install-Module 'Az.KeyVault' -Force
|
||||
Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ConnectAzureKeyVault {
|
||||
param(
|
||||
[string] $subscriptionId,
|
||||
[string] $tenantId,
|
||||
[string] $clientId,
|
||||
[SecureString] $clientSecret
|
||||
)
|
||||
try {
|
||||
$credential = New-Object PSCredential -argumentList $clientId, $clientSecret
|
||||
if ($script:azureRm210) {
|
||||
Add-AzureRmAccount -ServicePrincipal -Tenant $tenantId -Credential $credential -WarningAction SilentlyContinue | Out-Null
|
||||
Set-AzureRmContext -SubscriptionId $subscriptionId -Tenant $tenantId -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
else {
|
||||
Clear-AzContext -Scope Process
|
||||
Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue
|
||||
Connect-AzAccount -ServicePrincipal -Tenant $tenantId -Credential $credential -WarningAction SilentlyContinue | Out-Null
|
||||
Set-AzContext -SubscriptionId $subscriptionId -Tenant $tenantId -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null
|
||||
}
|
||||
$script:keyvaultConnectionExists = $true
|
||||
Write-Host "Successfully connected to Azure Key Vault."
|
||||
}
|
||||
catch {
|
||||
throw "Error trying to authenticate to Azure using Az. Error was $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function GetKeyVaultSecret {
|
||||
param (
|
||||
[string] $secretName,
|
||||
|
@ -189,8 +117,13 @@ function GetKeyVaultSecret {
|
|||
}
|
||||
|
||||
if (-not $script:keyvaultConnectionExists) {
|
||||
InstallKeyVaultModuleIfNeeded
|
||||
ConnectAzureKeyVault -subscriptionId $keyVaultCredentials.subscriptionId -tenantId $keyVaultCredentials.tenantId -clientId $keyVaultCredentials.clientId -clientSecret $keyVaultCredentials.clientSecret
|
||||
InstallAzModuleIfNeeded -name 'Az.KeyVault'
|
||||
try {
|
||||
ConnectAz -azureCredentials $keyVaultCredentials
|
||||
}
|
||||
catch {
|
||||
throw "Error trying to get secrets from Azure Key Vault. Error was $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$secretSplit = $secretName.Split('=')
|
||||
|
@ -201,11 +134,16 @@ function GetKeyVaultSecret {
|
|||
}
|
||||
|
||||
$value = $null
|
||||
if ($script:azureRm210) {
|
||||
$keyVaultSecret = Get-AzureKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret -ErrorAction SilentlyContinue
|
||||
try {
|
||||
$keyVaultSecret = Get-AzKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret
|
||||
}
|
||||
else {
|
||||
$keyVaultSecret = Get-AzKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret -ErrorAction SilentlyContinue
|
||||
catch {
|
||||
if ($keyVaultCredentials.PSObject.Properties.Name -eq 'ClientSecret') {
|
||||
throw "Error trying to get secrets from Azure Key Vault. Error was $($_.Exception.Message)"
|
||||
}
|
||||
else {
|
||||
throw "Error trying to get secrets from Azure Key Vault, maybe your Key Vault isn't setup for role based access control?. Error was $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
if ($keyVaultSecret) {
|
||||
if ($encrypted) {
|
||||
|
|
|
@ -45,15 +45,25 @@ try {
|
|||
else {
|
||||
throw "KeyVaultName is not specified in AzureCredentials nor in settings. Please specify it in one of them."
|
||||
}
|
||||
|
||||
$AzureCredentialParams = @{
|
||||
"ClientId" = $AzureCredentials.clientId
|
||||
"TenantId" = $AzureCredentials.tenantId
|
||||
}
|
||||
if ($AzureCredentials.PSobject.Properties.name -eq "clientSecret") {
|
||||
$AzureCredentialParams += @{
|
||||
"ClientSecret" = $AzureCredentials.clientSecret
|
||||
}
|
||||
}
|
||||
InstallAzModuleIfNeeded -name 'Az.Accounts'
|
||||
ConnectAz -azureCredentials $AzureCredentialParams
|
||||
|
||||
$description = "Signed with AL-Go for GitHub"
|
||||
$descriptionUrl = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY"
|
||||
|
||||
Write-Host "::group::Signing files"
|
||||
Invoke-SigningTool -KeyVaultName $AzureKeyVaultName `
|
||||
Invoke-SigningTool @AzureCredentialParams -KeyVaultName $AzureKeyVaultName `
|
||||
-CertificateName $settings.keyVaultCodesignCertificateName `
|
||||
-ClientId $AzureCredentials.clientId `
|
||||
-ClientSecret $AzureCredentials.clientSecret `
|
||||
-TenantId $AzureCredentials.tenantId `
|
||||
-FilesToSign $PathToFiles `
|
||||
-Description $description `
|
||||
-DescriptionUrl $descriptionUrl `
|
||||
|
|
|
@ -5,22 +5,22 @@
|
|||
Installs the dotnet signing tool.
|
||||
#>
|
||||
function Install-SigningTool() {
|
||||
. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)
|
||||
. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve)
|
||||
|
||||
# Create folder in temp directory with a unique name
|
||||
$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) "SigningTool-$(Get-Random)"
|
||||
# Create folder in temp directory with a unique name
|
||||
$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) "SigningTool-$(Get-Random)"
|
||||
|
||||
# Get version of the signing tool
|
||||
$version = Get-PackageVersion -PackageName "sign"
|
||||
# Get version of the signing tool
|
||||
$version = GetPackageVersion -PackageName "sign"
|
||||
|
||||
# Install the signing tool in the temp folder
|
||||
Write-Host "Installing signing tool version $version in $tempFolder"
|
||||
New-Item -ItemType Directory -Path $tempFolder | Out-Null
|
||||
dotnet tool install sign --version $version --tool-path $tempFolder | Out-Null
|
||||
# Install the signing tool in the temp folder
|
||||
Write-Host "Installing signing tool version $version in $tempFolder"
|
||||
New-Item -ItemType Directory -Path $tempFolder | Out-Null
|
||||
dotnet tool install sign --version $version --tool-path $tempFolder | Out-Null
|
||||
|
||||
# Return the path to the signing tool
|
||||
$signingTool = Join-Path -Path $tempFolder "sign.exe" -Resolve
|
||||
return $signingTool
|
||||
# Return the path to the signing tool
|
||||
$signingTool = Join-Path -Path $tempFolder "sign.exe" -Resolve
|
||||
return $signingTool
|
||||
}
|
||||
|
||||
<#
|
||||
|
@ -28,16 +28,17 @@ function Install-SigningTool() {
|
|||
Signs files in a given path using a certificate from Azure Key Vault.
|
||||
.DESCRIPTION
|
||||
Signs files in a given path using a certificate from Azure Key Vault.
|
||||
Connection to the Azure Key Vault can be done using a service principal or a managed identity.
|
||||
.PARAMETER KeyVaultName
|
||||
The name of the Azure Key Vault where the certificate is stored.
|
||||
.PARAMETER CertificateName
|
||||
The name of the certificate in the Azure Key Vault.
|
||||
.PARAMETER ClientId
|
||||
The client ID of the service principal used to authenticate with Azure Key Vault.
|
||||
[Optional] The client ID of the service principal used to authenticate with Azure Key Vault. If not specified, managed identity will be used.
|
||||
.PARAMETER ClientSecret
|
||||
The client secret of the service principal used to authenticate with Azure Key Vault.
|
||||
[Optional] The client secret of the service principal used to authenticate with Azure Key Vault. If not specified, managed identity will be used.
|
||||
.PARAMETER TenantId
|
||||
The tenant ID of the service principal used to authenticate with Azure Key Vault.
|
||||
[Optional] The tenant ID of the service principal used to authenticate with Azure Key Vault. If not specified, managed identity will be used.
|
||||
.PARAMETER FilesToSign
|
||||
The path to the file(s) to be signed. Supports wildcards.
|
||||
.PARAMETER Description
|
||||
|
@ -51,8 +52,10 @@ function Install-SigningTool() {
|
|||
.PARAMETER Verbosity
|
||||
The verbosity level of the signing tool.
|
||||
.EXAMPLE
|
||||
Invoke-SigningTool -KeyVaultName "my-key-vault" -CertificateName "my-certificatename" -ClientId "my-client-id" -ClientSecret "my-client-secret" -TenantId "my-tenant-id"
|
||||
Invoke-SigningTool -KeyVaultName "my-key-vault" -CertificateName "my-certificatename" -ClientId "my-client-id" -ClientSecret "my-client-secret" -TenantId "my-tenant-id" `
|
||||
-FilesToSign "C:\path\to\files\*.app" -Description "Signed with AL-Go for GitHub" -DescriptionUrl "github.com/myorg/myrepo"
|
||||
.EXAMPLE
|
||||
Invoke-SigningTool -KeyVaultName "my-key-vault" -CertificateName "my-certificatename" -FilesToSign "C:\path\to\files\*.app" -Description "Signed with AL-Go for GitHub" -DescriptionUrl "github.com/myorg/myrepo"
|
||||
#>
|
||||
function Invoke-SigningTool() {
|
||||
param(
|
||||
|
@ -60,11 +63,11 @@ function Invoke-SigningTool() {
|
|||
[string] $KeyVaultName,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $CertificateName,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string] $ClientId,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string] $ClientSecret,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string] $TenantId,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $FilesToSign,
|
||||
|
@ -83,19 +86,36 @@ function Invoke-SigningTool() {
|
|||
$signingToolExe = Install-SigningTool
|
||||
|
||||
# Sign files
|
||||
. $signingToolExe code azure-key-vault `
|
||||
--azure-key-vault-url "https://$KeyVaultName.vault.azure.net/" `
|
||||
--azure-key-vault-certificate $CertificateName `
|
||||
--azure-key-vault-client-id $ClientId `
|
||||
--azure-key-vault-client-secret $ClientSecret `
|
||||
--azure-key-vault-tenant-id $TenantId `
|
||||
--description $Description `
|
||||
--description-url $DescriptionUrl `
|
||||
--file-digest $DigestAlgorithm `
|
||||
--timestamp-digest $DigestAlgorithm `
|
||||
--timestamp-url $TimestampService `
|
||||
--verbosity $Verbosity `
|
||||
$FilesToSign
|
||||
if ($ClientId -and $ClientSecret -and $TenantId) {
|
||||
Write-Host "Invoking signing tool using clientId/clientSecret"
|
||||
. $signingToolExe code azure-key-vault `
|
||||
--azure-key-vault-url "https://$KeyVaultName.vault.azure.net/" `
|
||||
--azure-key-vault-certificate $CertificateName `
|
||||
--azure-key-vault-client-id $ClientId `
|
||||
--azure-key-vault-client-secret $ClientSecret `
|
||||
--azure-key-vault-tenant-id $TenantId `
|
||||
--description $Description `
|
||||
--description-url $DescriptionUrl `
|
||||
--file-digest $DigestAlgorithm `
|
||||
--timestamp-digest $DigestAlgorithm `
|
||||
--timestamp-url $TimestampService `
|
||||
--verbosity $Verbosity `
|
||||
$FilesToSign
|
||||
}
|
||||
else {
|
||||
Write-Host "Invoking signing tool using managed identity"
|
||||
. $signingToolExe code azure-key-vault `
|
||||
--azure-key-vault-url "https://$KeyVaultName.vault.azure.net/" `
|
||||
--azure-key-vault-certificate $CertificateName `
|
||||
--azure-key-vault-managed-identity $true `
|
||||
--description $Description `
|
||||
--description-url $DescriptionUrl `
|
||||
--file-digest $DigestAlgorithm `
|
||||
--timestamp-digest $DigestAlgorithm `
|
||||
--timestamp-url $TimestampService `
|
||||
--verbosity $Verbosity `
|
||||
$FilesToSign
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Invoke-SigningTool
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# These owners will be the default owners for everything in the repo.
|
||||
* @microsoft/d365-bc-engineering-systems
|
||||
# These owners will be the default owners for everything in the repo.
|
||||
* @microsoft/d365-bc-engineering-systems
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
- Issue 1105 Increment Version Number - repoVersion in .github/AL-Go-Settings.json is not updated
|
||||
- Issue 1073 Publish to AppSource - Automated validation: failure
|
||||
|
||||
### Dependencies to PowerShell modules
|
||||
|
||||
AL-Go for GitHub relies on specific PowerShell modules, and the minimum versions required for these modules are tracked in [Packages.json](https://raw.githubusercontent.com/microsoft/AL-Go/main/Actions/Packages.json) file. Should the installed modules on the GitHub runner not meet these minimum requirements, the necessary modules will be installed as needed.
|
||||
|
||||
### Support managed identities and federated credentials
|
||||
|
||||
All authentication context secrets now supports managed identities and federated credentials. See more [here](Scenarios/secrets.md). Furthermore, you can now use https://aka.ms/algosecrets#authcontext to learn more about the formatting of that secret.
|
||||
|
||||
### Business Central Performance Toolkit Test Result Viewer
|
||||
|
||||
In the summary after a Test Run, you now also have the result of performance tests.
|
||||
|
|
|
@ -13,11 +13,19 @@ This guide will take you through how to set up your AL-Go project with an Azure
|
|||
|
||||
How you do this might depend on which Certificate Authority you are getting your certificate from. DigiCert and GlobalSign have integrations with Azure Key Vault. You can follow [this guide](https://learn.microsoft.com/en-us/azure/key-vault/certificates/how-to-integrate-certificate-authority) on how to set up that integration if you are using one of those CAs. Once you have set up the integration, you can request a certificate from within your Azure Key Vault. If you are using another CA you can try following this guide to [Generate a CSR and Install a Certificate in Microsoft Azure Key Vault](https://www.ssl.com/how-to/generate-csr-install-certificate-microsoft-azure-key-vault/). If neither of those options work for you, please engage with your CA to get the certificate into the Key Vault.
|
||||
|
||||
2. Configure an Azure Key Vault access policy for the service principal that will be used for signing. At minimum, the account needs the following permissions:
|
||||
2. An Azure Key Vault can be set up for two different security models: Role Based Access Control (RBAC) (recommended) and Vault Access Policy. In order for AL-Go for GitHub to use the Key Vault, the following roles/permissions need to be assigned to the app registration or Managed Identity, on which the authentication is performed:
|
||||
|
||||
Role Based Access Control, roles needed:
|
||||
|
||||
- Key Vault Crypto User
|
||||
- Key Vault Certificate User
|
||||
|
||||
Vault Access Policy, permissions needed:
|
||||
|
||||
- Cryptographic Operations: Sign
|
||||
- Certificate Management Operations: Get
|
||||
![Key Vault Access Policies](https://github.com/microsoft/AL-Go/assets/117829001/c93375e0-ce5b-4aa0-a6b9-a845a87fddef)
|
||||
- Certificate permissions: Get
|
||||
|
||||
See more [here](https://aka.ms/algosecrets#azure_credentials).
|
||||
|
||||
## Setting up AL-Go for Code Signing
|
||||
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
# Secrets
|
||||
|
||||
The behavior of AL-Go for GitHub is very much controlled by settings and secrets.
|
||||
|
||||
To learn more about the settings used by AL-Go for GitHub, please navigate to [Settings](settings.md).
|
||||
|
||||
## Where are the secrets defined
|
||||
|
||||
Secrets in GitHub can be defined on the Organizational level, on the repository level or on an environment.
|
||||
|
||||
**Organizational secrets** are defined on your GitHub organization and can be shared with the repositories in your organization. For the free GitHub plan, organizational secrets can only be shared with public repositories.
|
||||
|
||||
**Repository secrets** are defined on the individual repository and you can define any number of secrets on the repository. If you define a secret on the repository level with the same name as an organizational secret, shared with the repository, the repository secret overrides the organizational secret.
|
||||
|
||||
**Environment secrets** are defined underneath an environment and is only available to the workflow during deployment to this environment. For the free GitHub plan, environments (and secrets obviously) are only available on public repositories.
|
||||
|
||||
See also [https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#about-secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#about-secrets).
|
||||
|
||||
> \[!NOTE\]
|
||||
> In AL-Go for GitHub you can also define your secrets in an Azure KeyVault, but you would still need to create one secret in GitHub called [Azure_Credentials](https://aka.ms/algosecrets#azure_credentials) to be able to access the Azure KeyVault.
|
||||
|
||||
## Important information about secrets (e.g. common mistakes...)
|
||||
|
||||
Please read the following topics carefully and make sure that you do not run into any of these common mistakes, which might cause some problems.
|
||||
|
||||
### Don't have secrets that are not secret
|
||||
|
||||
All secrets exposed to a repository will be masked (i.e. replaced with \*\*\*) in the workflow logs of that repository, ensuring that secret values are not exposed to the public. In GitHub, secrets are also not allowed to be transferred between jobs. If a variable transferred between two jobs contains a secret, you will see warnings like this in the output:
|
||||
|
||||
![image](https://github.com/microsoft/AL-Go/assets/10775043/b280360b-d3e8-47b9-8993-39b0de76d44a)
|
||||
|
||||
In this case, a secret with the value "windows" have been created and since the Initialization step transfers the githubRunner to the Build steps with the value "windows-latest", this will break AL-Go for GitHub.
|
||||
|
||||
So, don't have secrets that are not secrets as this might break core functionality in AL-Go for GitHub.
|
||||
|
||||
### Use compressed JSON
|
||||
|
||||
AL-Go for GitHub uses JSON structures for some secrets (like authentication contexts). AL-Go for GitHub will ensure that individual secret property values are masked in the log as well as the full JSON structure. When creating a JSON structure secret, it is important to use compressed JSON as GitHub will mask individual lines as well. This means that a non-compressed JSON structure will cause the curly bracket characters to be handled as secrets, breaking AL-Go for GitHub. In the logs you will see that the curly brackets are replaced with \*\*\*
|
||||
|
||||
![image](https://github.com/microsoft/AL-Go/assets/10775043/58bbc120-f36d-499d-8e6c-8cc87f55d918)
|
||||
|
||||
In this case, a secret is created with the following value:
|
||||
|
||||
```
|
||||
{
|
||||
"prop": "value"
|
||||
}
|
||||
```
|
||||
|
||||
So, don't have multi-line secrets, where individual lines are not secrets as this might break core functionality in AL-Go for GitHub.
|
||||
|
||||
### Only expose secrets that are necessary to your repositories that run on AL-Go for GitHub.
|
||||
|
||||
If your GitHub organization might have many organizational secrets, please only allow access to the secrets actually used by your AL-Go for GitHub repository. If any of the secrets made available to your repository contains multi-line secrets or have secrets, where the value is not really a secret, it might break core functionality in AL-Go for GitHub.
|
||||
|
||||
# Secrets
|
||||
|
||||
## <a id="Azure_Credentials"></a>**Azure_Credentials** -> Connect to Azure
|
||||
|
||||
By creating a secret called Azure_Credentials you can give your GitHub repository access to an Azure Key Vault, from which you can read secrets and use for managed signing of your apps. You can use a managed identity or an app registration (service to service) for authentication.
|
||||
|
||||
> \[!NOTE\]
|
||||
> In order to use a KeyVault for signing apps, it needs to be a premium SKU KeyVault. You can use this command to modify an existing KeyVault: `az keyvault update --set properties.sku.name=premium --name <KeyVaultName> --resource-group <ResourceGroupName>`
|
||||
|
||||
n Azure Key Vault can be set up for two different security models: Role Based Access Control (RBAC) (recommended) and Vault Access Policy. In order for AL-Go for GitHub to use the Key Vault, the following roles/permissions need to be assigned to the app registration or Managed Identity, on which the authentication is performed:
|
||||
|
||||
| Security Model | Read Secrets | Sign Apps |
|
||||
| :-- | :-- | :-- |
|
||||
| Role Based Access Control | Role: Key Vault Secrets User | Roles: Key Vault Crypto User + Key Vault Certificate User |
|
||||
| Vault Access Policy | Secret permissions: Get, List | Cryptographic Operations: Sign + Certificate permissions: Get |
|
||||
|
||||
### Managed Identity or App Registration
|
||||
|
||||
Whether you use a managed identity or an app registration for authentication, you need to assign the right permissions / roles to its Client Id (Application Id). For managed identities, the only authentication mechanism supported is federated credentials. For an app registration you can use federated credentials or a client Secret.
|
||||
|
||||
#### Federated credential
|
||||
|
||||
Using a federated credential, you need to register your GitHub repository in your managed identity under settings -> federated credentials or in the app registration under Certificates & Secrets. This registration will allow AL-Go for GitHub running in this repository to authenticate without the Client Secret stored. You still need to create a secret containing the clientId and the tenantId. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate.
|
||||
|
||||
Example: `{"keyVaultName":"MyKeyVault","clientId":"ed79570c-0384-4826-8099-bf0577af6667","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045"}`
|
||||
|
||||
#### ClientSecret
|
||||
|
||||
ClientSecret can only be used using an app registration. Under Certificates & Secrets in the app registration, you need to create a Client Secret, which you can specify in the AuthContext secret in AL-Go for GitHub. With the ClientId and ClientSecret, anybody can authenticate and perform actions as the connected user inside Business Central.
|
||||
|
||||
Example: `{"keyVaultName":"MyKeyVault","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045"}`
|
||||
|
||||
With this setup, you can create a setting called `keyVaultCodesignCertificateName` containing the name of the imported certificate in your Key Vault in order for AL-Go for GitHub to sign your apps.
|
||||
|
||||
## <a id="AuthContext"></a>**AuthContext** -> Deploy to an environment
|
||||
|
||||
Whenever AL-Go for GitHub is doing to deploy to an environment, it will need an AuthContext secret. The AuthContext secret can be provided underneath the environment in GitHub. If you are using a private repository in the free GitHub plan, you do not have environments. Then you can create an AuthContext secret in the repository. If you have multiple environments, you can create different AuthContext secrets by using the environment name followed by an underscore and AuthContext (f.ex. **QA_AuthContext**).
|
||||
|
||||
### Managed identity
|
||||
|
||||
Managed identities cannot be used for deploying to a Business Central environment as this is not an Azure resource
|
||||
|
||||
### Impersonation/RefreshToken
|
||||
|
||||
Specifying a RefreshToken allows AL-Go for GitHub to get access to impersonate the user who created the refresh token and act on behalf of that user on the scopes for which the refresh token was created. In this case, access is given to act as the user in Business Central.
|
||||
|
||||
Providing an AuthContext secret with a refreshtoken typically allows you to get access for 90 days. After the 90 days, you need to refresh the AuthContext secret with a new refreshToken. Note that anybody with the refreshToken can get access to call the API on behalf of the user, it doesn't have to be inside a workflow/pipeline.
|
||||
|
||||
Example: `{"tenantId":"d630ce39-5a0c-41ec-bf0d-6758ad558f0c","scopes":"https://api.businesscentral.dynamics.com/","RefreshToken":"0.AUUAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_s6Eo4YOI","clientId":"1950a258-227b-4e31-a9cf-717495945fc2"}`
|
||||
|
||||
### App Registration (Service to service authentication)
|
||||
|
||||
In order to use an app registration for publishing apps to Business Central, you need to register the ClientId (Application Id) of is app registration inside Business Central. This will create a user inside Business Central and you need to give the following permissions to that user: D365 AUTOMATION and EXTEN. MGT. - ADMIN. After this, there are two ways you can authenticate, either using Federated credential or using a Client Secret.
|
||||
|
||||
#### Federated credential
|
||||
|
||||
Using a federated credential, you need to register your GitHub repository in the app registration under Certificates & Secrets. This registration will allow AL-Go for GitHub running in this repository to authenticate without the Client Secret stored. You still need to create a secret containing this information. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate.
|
||||
|
||||
Example:`{"tenantId":"d630ce39-5a0c-41ec-bf0d-6758ad558f0c","scopes":"https://api.businesscentral.dynamics.com/","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32"}`
|
||||
|
||||
#### Client Secret
|
||||
|
||||
Under Certificates & Secrets in the app registration, you can create a Client Secret, which you can specify in the AuthContext secret in AL-Go for GitHub. With the ClientId and ClientSecret, anybody can authenticate and perform actions as the connected user inside Business Central.
|
||||
|
||||
Example: `{"tenantId":"d630ce39-5a0c-41ec-bf0d-6758ad558f0c","scopes":"https://api.businesscentral.dynamics.com/","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge"}`
|
||||
|
||||
## <a id="AppSourceContext"></a>**AppSourceContext** -> Deliver to AppSource
|
||||
|
||||
Adding a secret called AppSourceContext to an AL-Go for GitHub repository from the AppSource template, enables automatic delivery to AppSource.
|
||||
|
||||
### Managed identity
|
||||
|
||||
Managed identities cannot be used for submitting to AppSource as the partner center API is not an Azure resource
|
||||
|
||||
### App Registration (Service to service authentication)
|
||||
|
||||
In order to use an app registration for publishing apps to AppSource, you need to register the ClientId (Application Id) of is app registration in Partner Center. After this, there are two ways you can authenticate, either using Federated credential or using a Client Secret.
|
||||
|
||||
#### Federated credential
|
||||
|
||||
Using a federated credential, you need to register your GitHub repository in the app registration under Certificates & Secrets. This registration will allow AL-Go for GitHub running in this repository to authenticate without the Client Secret stored. You still need to create a secret containing this information. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate.
|
||||
|
||||
Example:`{"clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","scopes":"https://api.partner.microsoft.com/.default"}`
|
||||
|
||||
#### Client Secret
|
||||
|
||||
Under Certificates & Secrets in the app registration, you can create a Client Secret, which you can specify in the AuthContext secret in AL-Go for GitHub. Note that who ever has access to the clientId and clientSecret can publish apps on AppSource on your behalf.
|
||||
|
||||
Example: `{"tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","scopes":"https://api.partner.microsoft.com/.default","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge"}`
|
||||
|
||||
## <a id="StorageContext"></a>**StorageContext** -> Deliver to storage
|
||||
|
||||
Adding a secret called StorageContext to an AL-Go for GitHub repository, enables automatic delivery to an Azure storage account.
|
||||
|
||||
In AL-Go for GitHub, the Storage Context can be specified in 5 different ways, 5 different authentication mechanism towards an Azure Storage Account.
|
||||
|
||||
### Managed Identity/Federated credential
|
||||
|
||||
As a storage account is an Azure resource, we can use managed identities. Managed identities are like virtual users in Azure, using federated credentials for authentication. Using a federated credential, you need to register your GitHub repository in the managed identity under Settings -> Federated Credentials. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate.
|
||||
|
||||
Example: `{"storageAccountName":"MyStorageName","clientId":"08b6d80c-68cf-48f9-a5ff-b054326e2ec3","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}`
|
||||
|
||||
### App Registration/Federated credential
|
||||
|
||||
An app registration with federated credential is harder to setup than a managed identity, but just as secure. The mechanism is the same for obtaining an ID_TOKEN and providing this as proof of authenticity towards the app registration.
|
||||
|
||||
Example: `{"storageAccountName":"MyStorageName","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}`
|
||||
|
||||
### App Registration/Client Secret
|
||||
|
||||
An app registration with a client Secret is less secure than using federated credentials. Who ever has access to the clientSecret has access to everything the app registration has access to, until you recycle the client Secret.
|
||||
|
||||
Example: `{"storageAccountName":"MyStorageName","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}`
|
||||
|
||||
### storageAccountName/sastoken
|
||||
|
||||
A sas token for a storage account can be setup to function in a limited timeframe, giving access to perform a certain number of tasks on the storage account. Who ever has access to the sastoken can perform these tasks on the storage account until it expires or you recycle the storage account key used to create the sastoken.
|
||||
|
||||
Example: `{"storageAccountName":"MyStorageName","sastoken":"sv=2022-11-02&ss=b&srt=sco&sp=rwdlaciytf&se=2024-08-06T20:22:08Z&st=2024-04-06T12:22:08Z&spr=https&sig=IZyIf5xxxxxxxxxxxxxxxxxxxxxtq7tj6b5I%3D","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}`
|
||||
|
||||
### storageAccountName/storageAccountKey
|
||||
|
||||
Using storageAccount Name and Key is by far the most unsecure way of authenticating to an Azure Storage Account. If ever compromised, people can do anything with these credentials, until the storageAccount key is cycled.
|
||||
|
||||
Example: `{"storageAccountName":"MyStorageName","storageAccountKey":"JHFZErCyfQ8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxStj7AHXQ==","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"} `
|
||||
|
||||
## <a id="GitHubPackagesContext"></a>**GitHubPackagesContext** -> Deliver to GitHub Packages
|
||||
|
||||
If you create a secret called GitHubPackagesContext, then AL-Go for GitHub will automagically deliver apps to this NuGet feed after every successful build. AL-Go for GitHub will also use this NuGet feed for dependency resolution when building apps, giving you automatic dependency resolution within all your apps.
|
||||
|
||||
Example: `{"token":"ghp_NDdI2ExxxxxxxxxxxxxxxxxAYQh","serverUrl":"https://nuget.pkg.github.com/mygithuborg/index.json"}`
|
|
@ -1,6 +1,8 @@
|
|||
# Settings
|
||||
|
||||
The behavior of AL-Go for GitHub is very much controlled by settings.
|
||||
The behavior of AL-Go for GitHub is very much controlled by settings and secrets.
|
||||
|
||||
To learn more about the secrets used by AL-Go for GitHub, please navigate to [Secrets](secrets.md).
|
||||
|
||||
## Where are the settings located
|
||||
|
||||
|
|
|
@ -12,6 +12,21 @@ Param(
|
|||
|
||||
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
|
||||
|
||||
function DownloadHelperFile {
|
||||
param(
|
||||
[string] $url,
|
||||
[string] $folder
|
||||
)
|
||||
|
||||
$prevProgressPreference = $ProgressPreference; $ProgressPreference = 'SilentlyContinue'
|
||||
$name = [System.IO.Path]::GetFileName($url)
|
||||
Write-Host "Downloading $name from $url"
|
||||
$path = Join-Path $folder $name
|
||||
Invoke-WebRequest -UseBasicParsing -uri $url -OutFile $path
|
||||
$ProgressPreference = $prevProgressPreference
|
||||
return $path
|
||||
}
|
||||
|
||||
try {
|
||||
Clear-Host
|
||||
Write-Host
|
||||
|
@ -25,17 +40,11 @@ Write-Host -ForegroundColor Yellow @'
|
|||
|
||||
'@
|
||||
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.CachePolicy = New-Object System.Net.Cache.RequestCachePolicy -argumentList ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore)
|
||||
$webClient.Encoding = [System.Text.Encoding]::UTF8
|
||||
$GitHubHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1'
|
||||
Write-Host "Downloading GitHub Helper module from $GitHubHelperUrl"
|
||||
$GitHubHelperPath = "$([System.IO.Path]::GetTempFileName()).psm1"
|
||||
$webClient.DownloadFile($GitHubHelperUrl, $GitHubHelperPath)
|
||||
$ALGoHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1'
|
||||
Write-Host "Downloading AL-Go Helper script from $ALGoHelperUrl"
|
||||
$ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1"
|
||||
$webClient.DownloadFile($ALGoHelperUrl, $ALGoHelperPath)
|
||||
$tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())"
|
||||
New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null
|
||||
$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder
|
||||
$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder
|
||||
DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null
|
||||
|
||||
Import-Module $GitHubHelperPath
|
||||
. $ALGoHelperPath -local
|
||||
|
|
|
@ -16,6 +16,21 @@ Param(
|
|||
|
||||
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
|
||||
|
||||
function DownloadHelperFile {
|
||||
param(
|
||||
[string] $url,
|
||||
[string] $folder
|
||||
)
|
||||
|
||||
$prevProgressPreference = $ProgressPreference; $ProgressPreference = 'SilentlyContinue'
|
||||
$name = [System.IO.Path]::GetFileName($url)
|
||||
Write-Host "Downloading $name from $url"
|
||||
$path = Join-Path $folder $name
|
||||
Invoke-WebRequest -UseBasicParsing -uri $url -OutFile $path
|
||||
$ProgressPreference = $prevProgressPreference
|
||||
return $path
|
||||
}
|
||||
|
||||
try {
|
||||
Clear-Host
|
||||
Write-Host
|
||||
|
@ -29,17 +44,11 @@ Write-Host -ForegroundColor Yellow @'
|
|||
|
||||
'@
|
||||
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.CachePolicy = New-Object System.Net.Cache.RequestCachePolicy -argumentList ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore)
|
||||
$webClient.Encoding = [System.Text.Encoding]::UTF8
|
||||
$GitHubHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1'
|
||||
Write-Host "Downloading GitHub Helper module from $GitHubHelperUrl"
|
||||
$GitHubHelperPath = "$([System.IO.Path]::GetTempFileName()).psm1"
|
||||
$webClient.DownloadFile($GitHubHelperUrl, $GitHubHelperPath)
|
||||
$ALGoHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1'
|
||||
Write-Host "Downloading AL-Go Helper script from $ALGoHelperUrl"
|
||||
$ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1"
|
||||
$webClient.DownloadFile($ALGoHelperUrl, $ALGoHelperPath)
|
||||
$tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())"
|
||||
New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null
|
||||
$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder
|
||||
$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder
|
||||
DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null
|
||||
|
||||
Import-Module $GitHubHelperPath
|
||||
. $ALGoHelperPath -local
|
||||
|
|
|
@ -24,6 +24,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -17,6 +17,7 @@ permissions:
|
|||
contents: read
|
||||
actions: read
|
||||
pages: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
workflowDepth: 1
|
||||
|
@ -252,7 +253,7 @@ jobs:
|
|||
with:
|
||||
shell: ${{ matrix.shell }}
|
||||
gitHubSecrets: ${{ toJson(secrets) }}
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects'
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext'
|
||||
|
||||
- name: Deploy to Business Central
|
||||
id: Deploy
|
||||
|
|
|
@ -34,6 +34,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -28,6 +28,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -40,6 +40,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -49,6 +49,7 @@ permissions:
|
|||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
concurrency: release
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -24,6 +24,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -20,6 +20,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -14,6 +14,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
@ -152,7 +153,7 @@ jobs:
|
|||
with:
|
||||
shell: ${{ matrix.shell }}
|
||||
gitHubSecrets: ${{ toJson(secrets) }}
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects'
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext'
|
||||
|
||||
- name: Get Artifacts for deployment
|
||||
uses: microsoft/AL-Go-Actions/GetArtifactsForDeployment@main
|
||||
|
|
|
@ -16,6 +16,7 @@ permissions:
|
|||
contents: read
|
||||
actions: read
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
workflowDepth: 1
|
||||
|
|
|
@ -18,6 +18,7 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -75,6 +75,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
ALGoOrgSettings: ${{ vars.ALGoOrgSettings }}
|
||||
|
|
|
@ -12,6 +12,21 @@ Param(
|
|||
|
||||
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
|
||||
|
||||
function DownloadHelperFile {
|
||||
param(
|
||||
[string] $url,
|
||||
[string] $folder
|
||||
)
|
||||
|
||||
$prevProgressPreference = $ProgressPreference; $ProgressPreference = 'SilentlyContinue'
|
||||
$name = [System.IO.Path]::GetFileName($url)
|
||||
Write-Host "Downloading $name from $url"
|
||||
$path = Join-Path $folder $name
|
||||
Invoke-WebRequest -UseBasicParsing -uri $url -OutFile $path
|
||||
$ProgressPreference = $prevProgressPreference
|
||||
return $path
|
||||
}
|
||||
|
||||
try {
|
||||
Clear-Host
|
||||
Write-Host
|
||||
|
@ -25,17 +40,11 @@ Write-Host -ForegroundColor Yellow @'
|
|||
|
||||
'@
|
||||
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.CachePolicy = New-Object System.Net.Cache.RequestCachePolicy -argumentList ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore)
|
||||
$webClient.Encoding = [System.Text.Encoding]::UTF8
|
||||
$GitHubHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1'
|
||||
Write-Host "Downloading GitHub Helper module from $GitHubHelperUrl"
|
||||
$GitHubHelperPath = "$([System.IO.Path]::GetTempFileName()).psm1"
|
||||
$webClient.DownloadFile($GitHubHelperUrl, $GitHubHelperPath)
|
||||
$ALGoHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1'
|
||||
Write-Host "Downloading AL-Go Helper script from $ALGoHelperUrl"
|
||||
$ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1"
|
||||
$webClient.DownloadFile($ALGoHelperUrl, $ALGoHelperPath)
|
||||
$tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())"
|
||||
New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null
|
||||
$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder
|
||||
$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder
|
||||
DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null
|
||||
|
||||
Import-Module $GitHubHelperPath
|
||||
. $ALGoHelperPath -local
|
||||
|
|
|
@ -16,6 +16,21 @@ Param(
|
|||
|
||||
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
|
||||
|
||||
function DownloadHelperFile {
|
||||
param(
|
||||
[string] $url,
|
||||
[string] $folder
|
||||
)
|
||||
|
||||
$prevProgressPreference = $ProgressPreference; $ProgressPreference = 'SilentlyContinue'
|
||||
$name = [System.IO.Path]::GetFileName($url)
|
||||
Write-Host "Downloading $name from $url"
|
||||
$path = Join-Path $folder $name
|
||||
Invoke-WebRequest -UseBasicParsing -uri $url -OutFile $path
|
||||
$ProgressPreference = $prevProgressPreference
|
||||
return $path
|
||||
}
|
||||
|
||||
try {
|
||||
Clear-Host
|
||||
Write-Host
|
||||
|
@ -29,17 +44,11 @@ Write-Host -ForegroundColor Yellow @'
|
|||
|
||||
'@
|
||||
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.CachePolicy = New-Object System.Net.Cache.RequestCachePolicy -argumentList ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore)
|
||||
$webClient.Encoding = [System.Text.Encoding]::UTF8
|
||||
$GitHubHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1'
|
||||
Write-Host "Downloading GitHub Helper module from $GitHubHelperUrl"
|
||||
$GitHubHelperPath = "$([System.IO.Path]::GetTempFileName()).psm1"
|
||||
$webClient.DownloadFile($GitHubHelperUrl, $GitHubHelperPath)
|
||||
$ALGoHelperUrl = 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1'
|
||||
Write-Host "Downloading AL-Go Helper script from $ALGoHelperUrl"
|
||||
$ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1"
|
||||
$webClient.DownloadFile($ALGoHelperUrl, $ALGoHelperPath)
|
||||
$tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())"
|
||||
New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null
|
||||
$GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder
|
||||
$ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder
|
||||
DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null
|
||||
|
||||
Import-Module $GitHubHelperPath
|
||||
. $ALGoHelperPath -local
|
||||
|
|
|
@ -24,6 +24,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -17,6 +17,7 @@ permissions:
|
|||
contents: read
|
||||
actions: read
|
||||
pages: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
workflowDepth: 1
|
||||
|
@ -266,7 +267,7 @@ jobs:
|
|||
with:
|
||||
shell: ${{ matrix.shell }}
|
||||
gitHubSecrets: ${{ toJson(secrets) }}
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects'
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext'
|
||||
|
||||
- name: Deploy to Business Central
|
||||
id: Deploy
|
||||
|
|
|
@ -34,6 +34,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -28,6 +28,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -40,6 +40,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -49,6 +49,7 @@ permissions:
|
|||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
concurrency: release
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -24,6 +24,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -6,6 +6,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -14,6 +14,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
@ -152,7 +153,7 @@ jobs:
|
|||
with:
|
||||
shell: ${{ matrix.shell }}
|
||||
gitHubSecrets: ${{ toJson(secrets) }}
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects'
|
||||
getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext'
|
||||
|
||||
- name: Get Artifacts for deployment
|
||||
uses: microsoft/AL-Go-Actions/GetArtifactsForDeployment@main
|
||||
|
|
|
@ -21,6 +21,7 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -16,6 +16,7 @@ permissions:
|
|||
contents: read
|
||||
actions: read
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
workflowDepth: 1
|
||||
|
|
|
@ -12,6 +12,7 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -18,6 +18,7 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
@ -75,6 +75,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
ALGoOrgSettings: ${{ vars.ALGoOrgSettings }}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'Global vars used for local test execution only.')]
|
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'All scenario tests have equal parameter set.')]
|
||||
Param(
|
||||
[switch] $github,
|
||||
[string] $githubOwner = $global:E2EgithubOwner,
|
||||
[string] $repoName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetTempFileName()),
|
||||
[string] $token = ($Global:SecureE2EPAT | Get-PlainText),
|
||||
[string] $pteTemplate = $global:pteTemplate,
|
||||
[string] $appSourceTemplate = $global:appSourceTemplate,
|
||||
[string] $adminCenterApiToken = ($global:SecureAdminCenterApiToken | Get-PlainText)
|
||||
)
|
||||
|
||||
Write-Host -ForegroundColor Yellow @'
|
||||
# ______ _ _ _ _____ _ _ _ _
|
||||
# | ____| | | | | | | / ____| | | | | (_) | |
|
||||
# | |__ ___ __| | ___ _ __ __ _| |_ ___ __| | | | _ __ ___ __| | ___ _ __ | |_ _ __ _| |___
|
||||
# | __/ _ \/ _` |/ _ \ '__/ _` | __/ _ \/ _` | | | | '__/ _ \/ _` |/ _ \ '_ \| __| |/ _` | / __|
|
||||
# | | | __/ (_| | __/ | | (_| | || __/ (_| | | |____| | | __/ (_| | __/ | | | |_| | (_| | \__ \
|
||||
# |_| \___|\__,_|\___|_| \__,_|\__\___|\__,_| \_____|_| \___|\__,_|\___|_| |_|\__|_|\__,_|_|___/
|
||||
#
|
||||
#
|
||||
#
|
||||
# This test uses the bcsamples-bingmaps.appsource repository and will deliver a new build of the app to AppSource.
|
||||
# The bcsamples-bingmaps.appsource repository is setup to use an Azure KeyVault for secrets and app signing.
|
||||
# The bcSamples-bingmaps.appsource repository is setup for continuous delivery to AppSource
|
||||
# This test will deliver another build of the latest app version already delivered to AppSource (without go-live)
|
||||
#
|
||||
# This test tests the following scenario:
|
||||
#
|
||||
# bcsamples-bingmaps.appsource is setup to use an Azure KeyVault for secrets and app signing
|
||||
# Access to the Azure KeyVault is using federated credentials (branches main and e2e)
|
||||
# bcsamples-bingmaps.appsource is using a Entra ID app registration for delivering to AppSource
|
||||
# The Entra ID app registration is using federated credentials (branches main and e2e)
|
||||
#
|
||||
# - Remove branch e2e in repository microsoft/bcsamples-bingmaps.appsource (if it exists)
|
||||
# - Create a new branch called e2e in repository microsoft/bcsamples-bingmaps.appsource (based on main)
|
||||
# - Update AL-Go System Files in branch e2e in repository microsoft/bcsamples-bingmaps.appsource
|
||||
# - Invoke CI/CD in branch e2e in repository microsoft/bcsamples-bingmaps.appsource
|
||||
# - Check that artifacts are created and signed
|
||||
# - Check that the app is delivered to AppSource
|
||||
# - Remove the branch e2e in repository microsoft/bcsamples-bingmaps.appsource
|
||||
#
|
||||
'@
|
||||
|
||||
$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
|
||||
|
||||
Remove-Module e2eTestHelper -ErrorAction SilentlyContinue
|
||||
Import-Module (Join-Path $PSScriptRoot "..\..\e2eTestHelper.psm1") -DisableNameChecking
|
||||
|
||||
$branch = "e2e"
|
||||
$template = "https://github.com/$appSourceTemplate"
|
||||
$repository = 'microsoft/bcsamples-bingmaps.appsource'
|
||||
|
||||
SetTokenAndRepository -github:$github -githubOwner $githubOwner -token $token -repository $repository
|
||||
$headers = @{
|
||||
"Authorization" = "token $token"
|
||||
"X-GitHub-Api-Version" = "2022-11-28"
|
||||
"Accept" = "application/vnd.github+json"
|
||||
}
|
||||
$existingBranch = gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/$repository/branches/$branch 2> $null | ConvertFrom-Json
|
||||
if ($existingBranch.PSObject.Properties.Name -eq 'Name' -and $existingBranch.Name -eq $branch) {
|
||||
Write-Host "Removing existing branch $branch"
|
||||
Invoke-RestMethod -Method Delete -Uri "https://api.github.com/repos/$repository/git/refs/heads/$branch" -Headers $headers
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
$latestSha = (gh api /repos/$repository/commits/main | ConvertFrom-Json).sha
|
||||
gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/$repository/git/refs -f ref=refs/heads/$branch -f sha=$latestSha
|
||||
|
||||
# Upgrade AL-Go System Files to test version
|
||||
RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -repository $repository -branch $branch | Out-Null
|
||||
|
||||
# Run CI/CD workflow
|
||||
$run = RunCICD -repository $repository -branch $branch -wait
|
||||
|
||||
# Check that workflow run uses federated credentials and signing was successful
|
||||
Test-LogContainsFromRun -repository $repository -runid $run.id -jobName 'Build Main App (Default) Main App (Default)' -stepName 'Sign' -expectedText 'Connecting to Azure using clientId and federated token'
|
||||
Test-LogContainsFromRun -repository $repository -runid $run.id -jobName 'Build Main App (Default) Main App (Default)' -stepName 'Sign' -expectedText 'Signing succeeded'
|
||||
|
||||
# Check that Deliver to AppSource uses federated credentials and that a new submission was created
|
||||
Test-LogContainsFromRun -repository $repository -runid $run.id -jobName 'Deliver to AppSource' -stepName 'Read secrets' -expectedText 'Query federated token'
|
||||
Test-LogContainsFromRun -repository $repository -runid $run.id -jobName 'Deliver to AppSource' -stepName 'Deliver' -expectedText 'New AppSource submission created'
|
||||
|
||||
# Test artifacts generated
|
||||
$artifacts = gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /repos/$repository/actions/runs/$($run.id)/artifacts | ConvertFrom-Json
|
||||
@($artifacts.artifacts.Name) -like "Library Apps-$branch-Apps-*.*.*.0" | Should -Be $true
|
||||
@($artifacts.artifacts.Name) -like "Main App-$branch-Apps-*.*.*.0" | Should -Be $true
|
||||
@($artifacts.artifacts.Name) -like "Main App-$branch-Dependencies-*.*.*.0" | Should -Be $true
|
||||
|
||||
Write-Host "Download build artifacts"
|
||||
invoke-gh run download $run.id --repo $repository --dir 'signedApps'
|
||||
|
||||
$noOfApps = 0
|
||||
Get-Item "signedApps/Main App-$branch-Apps-*.*.*.0/*.app" | ForEach-Object {
|
||||
$appFile = $_.FullName
|
||||
Write-Host "Check that $appFile was signed"
|
||||
[System.Text.Encoding]::Ascii.GetString([System.IO.File]::ReadAllBytes($appFile)).indexof('DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA') | Should -BeGreaterThan -1
|
||||
$noOfApps++
|
||||
}
|
||||
# Check that two apps were signed
|
||||
$noOfApps | Should -Be 2
|
||||
|
||||
Invoke-RestMethod -Method Delete -Uri "https://api.github.com/repos/$repository/git/refs/heads/$branch" -Headers $headers
|
Загрузка…
Ссылка в новой задаче