diff --git a/scripts/Invoke-CIJob.ps1 b/scripts/Invoke-CIJob.ps1 index bf06162..f0e56a4 100644 --- a/scripts/Invoke-CIJob.ps1 +++ b/scripts/Invoke-CIJob.ps1 @@ -100,6 +100,11 @@ param( # Prerequisite: Git for Windows installed in template. [switch] $UseGitClone, + # Credential Manager target for Gitea PAT used by -UseGitClone (private repos). + # Store with: New-StoredCredential -Target 'GiteaPAT' -UserName '' -Password '' -Persist LocalMachine + # Ignored when -UseGitClone is not set. + [string] $GiteaCredentialTarget = 'GiteaPAT', + [string] $TemplatePath = $env:GITEA_CI_TEMPLATE_PATH, [string] $SnapshotName = $(if ($env:GITEA_CI_SNAPSHOT_NAME) { $env:GITEA_CI_SNAPSHOT_NAME } else { 'BaseClean' }), @@ -375,9 +380,9 @@ try { } if ($UseGitClone) { # §3.3: Clone in VM — pass URL/branch/commit instead of HostSourceDir - # PAT will be read from Credential Manager inside Invoke-RemoteBuild (§1.5) - $remoteBuildParams['CloneUrl'] = $RepoUrl - $remoteBuildParams['CloneBranch'] = $Branch + $remoteBuildParams['CloneUrl'] = $RepoUrl + $remoteBuildParams['CloneBranch'] = $Branch + $remoteBuildParams['GiteaCredentialTarget'] = $GiteaCredentialTarget if ($Commit -ne '') { $remoteBuildParams['CloneCommit'] = $Commit } if ($Submodules) { $remoteBuildParams['CloneSubmodules'] = $true } } diff --git a/scripts/Invoke-RemoteBuild.ps1 b/scripts/Invoke-RemoteBuild.ps1 index 710c243..78af43c 100644 --- a/scripts/Invoke-RemoteBuild.ps1 +++ b/scripts/Invoke-RemoteBuild.ps1 @@ -99,6 +99,11 @@ param( # [Mode 2] Clone with --recurse-submodules. Ignored if HostSourceDir provided. [switch] $CloneSubmodules, + # [Mode 2] Credential Manager target for Gitea PAT (private repos). + # Store with: New-StoredCredential -Target 'GiteaPAT' -UserName '' -Password '' -Persist LocalMachine + # Leave empty for public repos (no auth needed). + [string] $GiteaCredentialTarget = 'GiteaPAT', + [string] $Configuration = 'Release', # Custom build command to run inside the VM (working dir = GuestWorkDir). @@ -217,49 +222,62 @@ try { # PAT (if needed) read from Credential Manager inside session, never logged Write-Host "[Invoke-RemoteBuild] Cloning repository in guest (§3.3)..." - # Read PAT from Credential Manager (for private repos) - # This runs in the HOST session, but PAT is passed securely to guest session only - $gitPat = $null - try { - # Try to load PAT from Credential Manager target 'git:https://gitea.emulab.it/...' or similar - # For now, we assume public or ssh-key auth; if needed, caller provides CI_GIT_PAT env - # In future: $gitPat = (Get-StoredCredential -Target 'git:...' -ErrorAction SilentlyContinue).GetNetworkCredential().Password - } catch { - Write-Warning "[Invoke-RemoteBuild] Could not read git PAT from Credential Manager (proceeding without auth)." + # Read PAT from host Credential Manager (§1.5: never in argv or logs) + $gitPatUser = $null + $gitPatPass = $null + if (-not [string]::IsNullOrWhiteSpace($GiteaCredentialTarget)) { + try { + $stored = Get-StoredCredential -Target $GiteaCredentialTarget -ErrorAction Stop + $gitPatUser = $stored.UserName + $gitPatPass = $stored.GetNetworkCredential().Password + Write-Host "[Invoke-RemoteBuild] PAT loaded from Credential Manager ($GiteaCredentialTarget)." + } catch { + Write-Warning "[Invoke-RemoteBuild] Could not read PAT from Credential Manager '$GiteaCredentialTarget' — proceeding without auth (public repo only)." + } } # Clone in guest via WinRM Invoke-Command -Session $session -ScriptBlock { - param($cloneUrl, $branch, $commit, $workDir, $pat, $submodules) - # Set PAT as env var (never in argv or logs) - if ($pat) { - $env:GITLAB_PRIVATE_TOKEN = $pat # or appropriate token name for your git server - } + param($cloneUrl, $branch, $commit, $workDir, $patUser, $patPass, $submodules) Set-Location (Split-Path $workDir -Parent) $gitArgs = @('clone', '--depth', '1', '--branch', $branch) if ($submodules) { $gitArgs += '--recurse-submodules' } $gitArgs += @($cloneUrl, (Split-Path $workDir -Leaf)) - Write-Host "[Guest] Cloning $cloneUrl (branch: $branch)..." - & git @gitArgs 2>&1 | ForEach-Object { Write-Host "[Guest Clone] $_" } - if ($LASTEXITCODE -ne 0) { - throw "git clone failed in guest (exit $LASTEXITCODE)" + # Credentials via temp .git-credentials file (§1.5: token never in URL, argv, or logs) + $tmpCreds = $null + if ($patUser -and $patPass) { + $uri = [uri]$cloneUrl + $credLine = "$($uri.Scheme)://${patUser}:${patPass}@$($uri.Host)" + $tmpCreds = "C:\CI\tmp_gc_$([guid]::NewGuid().ToString('N'))" + [System.IO.File]::WriteAllText($tmpCreds, $credLine) + $gitArgs = @('-c', "credential.helper=store --file $tmpCreds") + $gitArgs } - # Checkout specific commit if provided - if ($commit) { - Write-Host "[Guest] Checking out commit: $commit" - Set-Location $workDir - & git checkout $commit 2>&1 | ForEach-Object { Write-Host "[Guest Checkout] $_" } + try { + Write-Host "[Guest] Cloning $cloneUrl (branch: $branch)..." + & git @gitArgs 2>&1 | ForEach-Object { Write-Host "[Guest Clone] $_" } if ($LASTEXITCODE -ne 0) { - throw "git checkout $commit failed in guest (exit $LASTEXITCODE)" + throw "git clone failed in guest (exit $LASTEXITCODE)" + } + + # Checkout specific commit if provided + if ($commit) { + Write-Host "[Guest] Checking out commit: $commit" + Set-Location $workDir + & git checkout $commit 2>&1 | ForEach-Object { Write-Host "[Guest Checkout] $_" } + if ($LASTEXITCODE -ne 0) { + throw "git checkout $commit failed in guest (exit $LASTEXITCODE)" + } + } + } finally { + # Always delete credentials file (§1.5: cleanup) + if ($tmpCreds -and (Test-Path $tmpCreds)) { + Remove-Item $tmpCreds -Force -ErrorAction SilentlyContinue } } - - # Clear PAT from environment (§1.5: cleanup) - if ($env:GITLAB_PRIVATE_TOKEN) { Remove-Item env:GITLAB_PRIVATE_TOKEN -ErrorAction SilentlyContinue } - } -ArgumentList $CloneUrl, $CloneBranch, $CloneCommit, $GuestWorkDir, $gitPat, $CloneSubmodules.IsPresent + } -ArgumentList $CloneUrl, $CloneBranch, $CloneCommit, $GuestWorkDir, $gitPatUser, $gitPatPass, $CloneSubmodules.IsPresent Write-Host "[Invoke-RemoteBuild] Guest clone complete." }