From eb6fdfb2ca34642e914edf136a3b25218f52e17b Mon Sep 17 00:00:00 2001 From: Simone Date: Sun, 10 May 2026 22:03:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(=C2=A73.3):=20Implement=20Gitea=20PAT=20au?= =?UTF-8?q?th=20for=20guest-side=20git=20clone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Credential Manager target 'GiteaPAT' (default) read on host, passed securely to guest WinRM session. Token never appears in argv, URL, or logs. Guest auth flow (§1.5 compliant): 1. Host reads PAT via Get-StoredCredential -Target GiteaPAT 2. Username + password passed as ArgumentList to Invoke-Command 3. Guest writes temp .git-credentials file (https://user:token@host) 4. git clone uses: -c 'credential.helper=store --file ' 5. finally{} deletes credentials file regardless of clone success/fail Setup (one-time on host): New-StoredCredential -Target 'GiteaPAT' -UserName '' ` -Password '' -Persist LocalMachine Invoke-CIJob.ps1: added -GiteaCredentialTarget param (default: 'GiteaPAT'), passed through to Invoke-RemoteBuild when -UseGitClone is set. Co-Authored-By: Claude Sonnet 4.6 --- scripts/Invoke-CIJob.ps1 | 11 +++-- scripts/Invoke-RemoteBuild.ps1 | 74 +++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 31 deletions(-) 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." }