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:
Freddy Kristiansen 2024-06-25 15:47:20 +02:00 коммит произвёл GitHub
Родитель 7a66ed6da4
Коммит 9aa6e6ff03
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
54 изменённых файлов: 669 добавлений и 251 удалений

2
.github/workflows/E2E.yaml поставляемый
Просмотреть файл

@ -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

186
Scenarios/secrets.md Normal file
Просмотреть файл

@ -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