Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<add key="dotnet9" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" />
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
<add key="AzureGenevaMonitoring" value="https://pkgs.dev.azure.com/msblox/_packaging/AzureGenevaMonitoring/nuget/v3/index.json" />
</packageSources>
<disabledPackageSources />
</configuration>
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<AwesomeAssertionsVersion>9.0.0</AwesomeAssertionsVersion>
<MicrosoftBuildVersion>17.7.0</MicrosoftBuildVersion>
<MSBuildProjectCreationVersion>10.0.0</MSBuildProjectCreationVersion>
<OpenTelemetryAuditGenevaVersion>2.5.2</OpenTelemetryAuditGenevaVersion>
</PropertyGroup>
</Project>
6 changes: 6 additions & 0 deletions eng/create-baridtag.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ set-strictmode -version 2.0
$ErrorActionPreference = 'Stop'

. $PSScriptRoot\common\tools.ps1
. $PSScriptRoot\validation\audit-logging.ps1
$darc = Get-Darc

$arcadeSdkPackageName = 'Microsoft.DotNet.Arcade.Sdk'
Expand All @@ -18,9 +19,14 @@ $assetData = & $darc get-asset `
--output-format json `
| convertFrom-Json

Write-AuditLog -OperationName "QueryBARAssets" -OperationCategory "ResourceManagement" -OperationType "Read" `
-OperationResult "Success" -TargetResourceType "BAR_Asset" -TargetResourceId "$arcadeSdkPackageName@$arcadeSdkVersion"

# Get the BAR Build ID for the version of Arcade we are validating
$barBuildId = $assetData.build.id
$azdoBuildId = $assetData.build.azdoBuildId

Write-Host "##vso[build.addbuildtag]ValidatingBarIds $barBuildId"
Write-Host "##vso[build.addbuildtag]ValidatingAzDOBuild $azdoBuildId"
Write-AuditLog -OperationName "CreateBuildTag" -OperationCategory "ResourceManagement" -OperationType "Create" `
-OperationResult "Success" -TargetResourceType "AzdoBuildTag" -TargetResourceId "Bar:$barBuildId/Azdo:$azdoBuildId"
279 changes: 279 additions & 0 deletions eng/validation/audit-logging.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
# OTel Audit Logging Helper for arcade-validation pipeline scripts
# Emits structured audit log entries via Azure DevOps pipeline logging commands.
# These entries are picked up by Geneva Agent on 1ES hosted agents.

# Service Tree IDs: GitHub vs AzDO mirror
$GitHubServiceTreeId = "b3bbd815-183a-4142-8056-3a676d687f71"
$AzDOServiceTreeId = "8835b1f3-0d22-4e28-bae0-65da04655ed4"

# Resolve the correct Service Tree ID based on environment
# Priority: env var override > auto-detect (TF_BUILD = AzDO, otherwise GitHub)
if ($env:OTEL_AUDIT_SERVICE_TREE_ID) {
$AuditServiceTreeId = $env:OTEL_AUDIT_SERVICE_TREE_ID
} elseif ($env:TF_BUILD) {
$AuditServiceTreeId = $AzDOServiceTreeId
} else {
$AuditServiceTreeId = $GitHubServiceTreeId
}

function Get-AgentIpAddress {
try {
if ($IsWindows -or ($PSVersionTable.PSVersion.Major -le 5)) {
# Windows: use Get-NetIPAddress
$ip = (Get-NetIPAddress -AddressFamily IPv4 -Type Unicast -ErrorAction SilentlyContinue |
Where-Object { $_.IPAddress -ne '127.0.0.1' -and $_.IPAddress -notlike '169.254.*' } |
Select-Object -First 1).IPAddress
if ($ip) { return $ip }
} else {
# Linux/macOS: use hostname or ip command
$ip = (hostname -I 2>/dev/null) -split '\s+' |
Where-Object { $_ -ne '127.0.0.1' -and $_ -notlike '169.254.*' -and $_ -match '^\d+\.\d+\.\d+\.\d+$' } |
Select-Object -First 1
if ($ip) { return $ip }
}
} catch {}
return "127.0.0.1"
}

function Write-AuditLog {
<#
.SYNOPSIS
Emits a structured audit log entry for a privileged operation.
.DESCRIPTION
Logs audit telemetry using Azure DevOps pipeline logging commands.
The structured data is captured by Geneva Agent for OTel Audit compliance.
.PARAMETER OperationName
PascalCase verb+noun name of the operation (e.g., PromoteBuildToChannel).
.PARAMETER OperationCategory
Category of the operation: ResourceManagement, KeyManagement, UserManagement,
RoleManagement, GroupManagement, PolicyManagement, Authorization, Authentication, Other.
.PARAMETER OperationType
Type: Create, Read, Update, Delete, Assign, Unassign, Other.
.PARAMETER OperationResult
Result: Success, Failure.
.PARAMETER CallerIdentity
Identity performing the operation (e.g., pipeline service account, UPN).
.PARAMETER TargetResourceType
Type of resource being acted upon (e.g., MaestroChannel, AzdoBuild, GitBranch).
.PARAMETER TargetResourceId
Identifier of the target resource.
.PARAMETER OperationAccessLevel
Permission required to execute the operation.
.PARAMETER CallerAccessLevels
Permissions the caller has.
.PARAMETER ResultDescription
Description of failure reason (required when OperationResult is Failure).
.PARAMETER CustomData
Hashtable of additional key-value pairs for context.
#>
param(
[Parameter(Mandatory=$true)]
[string]$OperationName,

[Parameter(Mandatory=$true)]
[ValidateSet("ResourceManagement", "KeyManagement", "UserManagement", "RoleManagement",
"GroupManagement", "PolicyManagement", "Authorization", "Authentication", "Other")]
[string]$OperationCategory,

[Parameter(Mandatory=$true)]
[ValidateSet("Create", "Read", "Update", "Delete", "Assign", "Unassign", "Other")]
[string]$OperationType,

[Parameter(Mandatory=$true)]
[ValidateSet("Success", "Failure")]
[string]$OperationResult,

[Parameter(Mandatory=$false)]
[string]$CallerIdentity = $env:BUILD_REQUESTEDFOR,

[Parameter(Mandatory=$true)]
[string]$TargetResourceType,

[Parameter(Mandatory=$true)]
[string]$TargetResourceId,

[Parameter(Mandatory=$false)]
[string]$OperationAccessLevel = "PipelineExecution",

[Parameter(Mandatory=$false)]
[string[]]$CallerAccessLevels = @("PipelineServiceAccount"),

[Parameter(Mandatory=$false)]
[string]$ResultDescription,

[Parameter(Mandatory=$false)]
[hashtable]$CustomData = @{}
)

# Build the audit record
$auditRecord = @{
ServiceTreeId = $AuditServiceTreeId
OperationName = $OperationName
OperationCategory = $OperationCategory
OperationType = $OperationType
OperationResult = $OperationResult
CallerIdentity = if ($CallerIdentity) { $CallerIdentity } else { "AzurePipelines" }
CallerAgent = "AzurePipelines/$($env:SYSTEM_TEAMPROJECT)/$($env:BUILD_DEFINITIONNAME)"
CallerIpAddress = (Get-AgentIpAddress)
TargetResourceType = $TargetResourceType
TargetResourceId = $TargetResourceId
OperationAccessLevel = $OperationAccessLevel
CallerAccessLevels = ($CallerAccessLevels -join ",")
Timestamp = (Get-Date -Format "o")
# Golden Schema fields
UserAgent = "ArcadeValidation/$($env:BUILD_BUILDNUMBER)"
AppId = if ($env:BUILD_REQUESTEDFORID) { $env:BUILD_REQUESTEDFORID } else { "local" }
TokenInfo = if ($env:TF_BUILD) { "AuthSchema=AzurePipelines" } else { "" }
# Environment context
MachineName = $env:COMPUTERNAME
BuildId = $env:BUILD_BUILDID
BuildNumber = $env:BUILD_BUILDNUMBER
Repository = $env:BUILD_REPOSITORY_NAME
SourceBranch = $env:BUILD_SOURCEBRANCH
}

if ($OperationResult -eq "Failure" -and $ResultDescription) {
$auditRecord["ResultDescription"] = $ResultDescription
}

foreach ($key in $CustomData.Keys) {
$auditRecord["Custom$key"] = $CustomData[$key]
}

# Emit as structured telemetry via pipeline logging
$jsonPayload = $auditRecord | ConvertTo-Json -Compress
Write-Host "##[section]OTelAudit: $OperationName ($OperationResult)"
Write-Host "##vso[task.logdetail id=$(New-Guid);name=OTelAudit;type=AuditRecord;state=Completed]$jsonPayload"

# Also write to pipeline timeline for visibility
if ($OperationResult -eq "Failure") {
Write-Warning "[OTel Audit] $OperationName FAILED: $ResultDescription"
} else {
Write-Host "[OTel Audit] $OperationName succeeded on $TargetResourceType/$TargetResourceId"
}
}

function Write-AuditLog-ChannelPromotion {
<#
.SYNOPSIS
Logs a Maestro channel promotion operation.
#>
param(
[Parameter(Mandatory=$true)][string]$ChannelName,
[Parameter(Mandatory=$true)][string]$BuildId,
[Parameter(Mandatory=$true)][ValidateSet("Success", "Failure")][string]$Result,
[Parameter(Mandatory=$false)][string]$ResultDescription
)

Write-AuditLog `
-OperationName "PromoteBuildToChannel" `
-OperationCategory "ResourceManagement" `
-OperationType "Update" `
-OperationResult $Result `
-TargetResourceType "MaestroChannel" `
-TargetResourceId $ChannelName `
-OperationAccessLevel "MaestroChannelAdmin" `
-CallerAccessLevels @("MaestroChannelAdmin", "AzdoPipelineToken") `
-ResultDescription $ResultDescription `
-CustomData @{ BuildId = $BuildId }
}

function Write-AuditLog-ChannelDeletion {
<#
.SYNOPSIS
Logs a Maestro default channel deletion operation.
#>
param(
[Parameter(Mandatory=$true)][string]$ChannelName,
[Parameter(Mandatory=$true)][string]$Repository,
[Parameter(Mandatory=$true)][string]$Branch,
[Parameter(Mandatory=$true)][ValidateSet("Success", "Failure")][string]$Result,
[Parameter(Mandatory=$false)][string]$ResultDescription
)

Write-AuditLog `
-OperationName "DeleteDefaultChannel" `
-OperationCategory "ResourceManagement" `
-OperationType "Delete" `
-OperationResult $Result `
-TargetResourceType "MaestroDefaultChannel" `
-TargetResourceId "$Repository@$Branch->$ChannelName" `
-OperationAccessLevel "MaestroChannelAdmin" `
-CallerAccessLevels @("MaestroChannelAdmin", "AzdoPipelineToken") `
-ResultDescription $ResultDescription
}

function Write-AuditLog-BuildRetention {
<#
.SYNOPSIS
Logs a build retention operation.
#>
param(
[Parameter(Mandatory=$true)][string]$BuildId,
[Parameter(Mandatory=$true)][string]$Project,
[Parameter(Mandatory=$true)][ValidateSet("Success", "Failure")][string]$Result,
[Parameter(Mandatory=$false)][string]$ResultDescription
)

Write-AuditLog `
-OperationName "RetainBuildPermanently" `
-OperationCategory "ResourceManagement" `
-OperationType "Update" `
-OperationResult $Result `
-TargetResourceType "AzdoBuild" `
-TargetResourceId "$Project/Build/$BuildId" `
-OperationAccessLevel "BuildAdmin" `
-CallerAccessLevels @("BuildAdmin", "SystemAccessToken") `
-ResultDescription $ResultDescription
}

function Write-AuditLog-BranchOperation {
<#
.SYNOPSIS
Logs a Git branch operation (create/delete).
#>
param(
[Parameter(Mandatory=$true)][string]$Repository,
[Parameter(Mandatory=$true)][string]$BranchName,
[Parameter(Mandatory=$true)][ValidateSet("Create", "Delete")][string]$OperationType,
[Parameter(Mandatory=$true)][ValidateSet("Success", "Failure")][string]$Result,
[Parameter(Mandatory=$false)][string]$ResultDescription
)

Write-AuditLog `
-OperationName "${OperationType}RemoteBranch" `
-OperationCategory "ResourceManagement" `
-OperationType $OperationType `
-OperationResult $Result `
-TargetResourceType "GitBranch" `
-TargetResourceId "$Repository/$BranchName" `
-OperationAccessLevel "GitPush" `
-CallerAccessLevels @("GitPush", "AzdoPipelineToken") `
-ResultDescription $ResultDescription
}

function Write-AuditLog-BuildInvocation {
<#
.SYNOPSIS
Logs a build invocation via Azure DevOps API.
#>
param(
[Parameter(Mandatory=$true)][string]$Project,
[Parameter(Mandatory=$true)][string]$PipelineId,
[Parameter(Mandatory=$true)][string]$SourceBranch,
[Parameter(Mandatory=$true)][ValidateSet("Success", "Failure")][string]$Result,
[Parameter(Mandatory=$false)][string]$ResultDescription
)

Write-AuditLog `
-OperationName "InvokeAzdoBuild" `
-OperationCategory "ResourceManagement" `
-OperationType "Create" `
-OperationResult $Result `
-TargetResourceType "AzdoPipeline" `
-TargetResourceId "$Project/Pipeline/$PipelineId" `
-OperationAccessLevel "QueueBuilds" `
-CallerAccessLevels @("QueueBuilds", "AzdoPipelineToken") `
-ResultDescription $ResultDescription `
-CustomData @{ SourceBranch = $SourceBranch }
}
15 changes: 15 additions & 0 deletions eng/validation/build-arcadewithrepo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ set-strictmode -version 2.0
$ErrorActionPreference = 'Stop'

. $PSScriptRoot\..\common\tools.ps1
. $PSScriptRoot\audit-logging.ps1
. $PSScriptRoot\validation-functions.ps1
$darc = & "$PSScriptRoot\get-darc.ps1"

Expand Down Expand Up @@ -159,6 +160,8 @@ $sha = Get-LatestBuildSha
## Clone the repo from git
Write-Host "Cloning '${global:githubRepoName}' from GitHub"
GitHub-Clone $global:githubRepoName $global:githubUser $global:githubUri
Write-AuditLog -OperationName "GitCloneWithCredentials" -OperationCategory "ResourceManagement" -OperationType "Read" `
-OperationResult "Success" -TargetResourceType "GitRepository" -TargetResourceId "${global:githubOrg}/${global:githubRepoName}"

## Check to see if branch exists and clean it up if it does
$branchExists = $false
Expand All @@ -180,13 +183,17 @@ if($null -ne $branchExists)
if($true -eq $global:pushBranchToGithub)
{
& $darc delete-default-channel --channel "General Testing" --branch $global:darcBranchName --repo $global:darcGitHubRepoName --github-pat $global:githubPAT --password $global:bartoken
Write-AuditLog-ChannelDeletion -ChannelName "General Testing" -Repository $global:darcGitHubRepoName -Branch $global:darcBranchName -Result "Success"
Git-Command $global:githubRepoName push origin --delete $global:targetBranch
Write-AuditLog-BranchOperation -Repository $global:darcGitHubRepoName -BranchName $global:targetBranch -OperationType "Delete" -Result "Success"
}
else
{
& $darc delete-default-channel --channel "General Testing" --branch $global:darcBranchName --repo $global:darcAzDORepoName --azdev-pat $global:azdoToken --password $global:bartoken
Write-AuditLog-ChannelDeletion -ChannelName "General Testing" -Repository $global:darcAzDORepoName -Branch $global:darcBranchName -Result "Success"
Git-Command $global:githubRepoName remote add $remoteName $global:azdoUri
Git-Command $global:githubRepoName push $remoteName --delete $global:targetBranch
Write-AuditLog-BranchOperation -Repository $global:darcAzDORepoName -BranchName $global:targetBranch -OperationType "Delete" -Result "Success"
}
}
catch
Expand Down Expand Up @@ -222,6 +229,7 @@ if($true -eq $global:pushBranchToGithub)
{
## Push branch to github
Git-Command $global:githubRepoName push origin HEAD
Write-AuditLog-BranchOperation -Repository $global:darcGitHubRepoName -BranchName $global:targetBranch -OperationType "Create" -Result "Success"

## Assign darcRepoName value
$global:darcRepoName = $global:darcGitHubRepoName
Expand All @@ -240,17 +248,20 @@ else
}
## push to remote
Git-Command $global:githubRepoName push $global:remoteName $global:targetBranch
Write-AuditLog-BranchOperation -Repository $global:darcAzDORepoName -BranchName $global:targetBranch -OperationType "Create" -Result "Success"

## Assign darcRepoName value
$global:darcRepoName = $global:darcAzDORepoName
}

## Add default channel from that AzDO repo and branch to "General Testing"
& $darc add-default-channel --channel "General Testing" --branch $global:darcBranchName --repo $global:darcRepoName --azdev-pat $global:azdoToken --github-pat $global:githubPAT --password $global:bartoken
Write-AuditLog-ChannelPromotion -ChannelName "General Testing" -BuildId "DefaultChannel" -Result "Success"

## Run an official build of the branch using the official pipeline
Write-Host "Invoking build on Azure DevOps"
$buildId = Invoke-AzDOBuild
Write-AuditLog-BuildInvocation -Project $global:azdoProject -PipelineId $global:buildDefinitionId -SourceBranch $global:targetBranch -Result "Success"

## Output summary of references for investigations
Write-Host "Arcade Version: ${global:arcadeSdkVersion}"
Expand Down Expand Up @@ -290,12 +301,16 @@ try
if($true -eq $global:pushBranchToGithub)
{
& $darc delete-default-channel --channel "General Testing" --branch $global:darcBranchName --repo $global:darcGitHubRepoName --github-pat $global:githubPAT --password $global:bartoken
Write-AuditLog-ChannelDeletion -ChannelName "General Testing" -Repository $global:darcGitHubRepoName -Branch $global:darcBranchName -Result "Success"
Git-Command $global:githubRepoName push origin --delete $global:targetBranch
Write-AuditLog-BranchOperation -Repository $global:darcGitHubRepoName -BranchName $global:targetBranch -OperationType "Delete" -Result "Success"
}
else
{
& $darc delete-default-channel --channel "General Testing" --branch $global:darcBranchName --repo $global:darcAzDORepoName --azdev-pat $global:azdoToken --password $global:bartoken
Write-AuditLog-ChannelDeletion -ChannelName "General Testing" -Repository $global:darcAzDORepoName -Branch $global:darcBranchName -Result "Success"
Git-Command $global:githubRepoName push $global:remoteName --delete $global:targetBranch
Write-AuditLog-BranchOperation -Repository $global:darcAzDORepoName -BranchName $global:targetBranch -OperationType "Delete" -Result "Success"
}
}
catch
Expand Down
Loading
Loading