初始化系统融合软件代码仓库
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Initialize-XtrhImageVerification {
|
||||
param([Parameter(Mandatory)][string]$CredentialFile)
|
||||
# 私有文件只读入内存。日志、stdout、命令参数和联调记录都不携带凭证正文。
|
||||
$privateText = [IO.File]::ReadAllText([IO.Path]::GetFullPath($CredentialFile))
|
||||
$match = [regex]::Match($privateText, '(?m)^fusion\.token\s*=\s*([^\r\n]+)')
|
||||
if (-not $match.Success -or $match.Groups[1].Value.Trim().Length -lt 16) { throw '集群私有凭证文件无效。' }
|
||||
$script:imageVerificationCredential = $match.Groups[1].Value.Trim()
|
||||
$handler = [Net.Http.HttpClientHandler]::new()
|
||||
$handler.AllowAutoRedirect = $false
|
||||
$handler.UseCookies = $false
|
||||
$script:imageVerificationClient = [Net.Http.HttpClient]::new($handler, $true)
|
||||
$script:imageVerificationClient.Timeout = [TimeSpan]::FromSeconds(5)
|
||||
$script:imageVerificationClient.MaxResponseContentBufferSize = 8 * 1024 * 1024
|
||||
}
|
||||
|
||||
function Get-XtrhImageDigest {
|
||||
param([Parameter(Mandatory)][AllowEmptyCollection()][byte[]]$Bytes)
|
||||
$sha = [Security.Cryptography.SHA256]::Create()
|
||||
try { return [BitConverter]::ToString($sha.ComputeHash($Bytes)).Replace('-', '').ToLowerInvariant() }
|
||||
finally { $sha.Dispose() }
|
||||
}
|
||||
|
||||
function Get-XtrhImageSignature {
|
||||
param([Parameter(Mandatory)][string]$Canonical)
|
||||
$mac = [Security.Cryptography.HMACSHA256]::new([Text.Encoding]::UTF8.GetBytes($script:imageVerificationCredential))
|
||||
try { return [BitConverter]::ToString($mac.ComputeHash([Text.Encoding]::UTF8.GetBytes($Canonical))).Replace('-', '').ToLowerInvariant() }
|
||||
finally { $mac.Dispose() }
|
||||
}
|
||||
|
||||
function Invoke-XtrhImageSigned {
|
||||
param([Parameter(Mandatory)][string]$BaseUrl, [Parameter(Mandatory)][string]$Path,
|
||||
[ValidateSet('GET','POST','PATCH')][string]$Method = 'GET', $Payload = $null)
|
||||
$base = [Uri]$BaseUrl
|
||||
if ($base.Scheme -notin @('http','https') -or $base.UserInfo -or $base.Query -or $base.Fragment -or $base.AbsolutePath -ne '/') {
|
||||
throw '服务根地址格式无效。'
|
||||
}
|
||||
if ($Path -notmatch '^/' -or $Path -match '[\r\n]') { throw '签名接口路径无效。' }
|
||||
# 空数组不能通过 PowerShell 表达式管道返回,否则会被展开为 null。
|
||||
[byte[]]$body = [byte[]]::new(0)
|
||||
if ($null -ne $Payload) { $body = [Text.Encoding]::UTF8.GetBytes(($Payload | ConvertTo-Json -Depth 12 -Compress)) }
|
||||
$nonce = [Guid]::NewGuid().ToString()
|
||||
$time = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds().ToString()
|
||||
$digest = Get-XtrhImageDigest -Bytes $body
|
||||
$signature = Get-XtrhImageSignature -Canonical "request`n$Method`n$Path`n$time`n$nonce`n$digest"
|
||||
$request = [Net.Http.HttpRequestMessage]::new([Net.Http.HttpMethod]::new($Method), $BaseUrl.TrimEnd('/') + $Path)
|
||||
try {
|
||||
[void]$request.Headers.TryAddWithoutValidation('X-Xtrh-Time', $time)
|
||||
[void]$request.Headers.TryAddWithoutValidation('X-Xtrh-Nonce', $nonce)
|
||||
[void]$request.Headers.TryAddWithoutValidation('X-Xtrh-Signature', $signature)
|
||||
$request.Content = [Net.Http.ByteArrayContent]::new($body)
|
||||
$request.Content.Headers.ContentType = [Net.Http.Headers.MediaTypeHeaderValue]::new('application/json')
|
||||
try { $response = $script:imageVerificationClient.SendAsync($request).GetAwaiter().GetResult() }
|
||||
catch { throw '服务请求失败,未输出私有请求头或响应内容。' }
|
||||
try {
|
||||
[byte[]]$bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
|
||||
$status = [int]$response.StatusCode
|
||||
$expected = Get-XtrhImageSignature -Canonical ("response`n$nonce`n$status`n" + (Get-XtrhImageDigest -Bytes $bytes))
|
||||
$headers = [Collections.Generic.IEnumerable[string]]$null
|
||||
if (-not $response.Headers.TryGetValues('X-Xtrh-Response-Signature', [ref]$headers)) { throw '响应缺少服务身份签名。' }
|
||||
$actual = @($headers)[0]
|
||||
# 固定长度与逐字节累计比较;JPEG 必须签其原始字节,不能按文本转换。
|
||||
$different = $actual.Length -bxor $expected.Length
|
||||
if ($actual.Length -eq $expected.Length) {
|
||||
for ($i = 0; $i -lt $expected.Length; $i++) { $different = $different -bor ([int]$actual[$i] -bxor [int]$expected[$i]) }
|
||||
}
|
||||
if ($different -ne 0) { throw '响应服务身份认证失败。' }
|
||||
return [pscustomobject]@{ Status = $status; Bytes = $bytes; ContentType = [string]$response.Content.Headers.ContentType }
|
||||
} finally { $response.Dispose() }
|
||||
} finally { $request.Dispose() }
|
||||
}
|
||||
|
||||
function ConvertFrom-XtrhImageJson {
|
||||
param([Parameter(Mandatory)]$Response)
|
||||
if ($Response.Status -ne 200) { throw ('签名服务接口返回 HTTP ' + $Response.Status) }
|
||||
return [Text.Encoding]::UTF8.GetString($Response.Bytes) | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Get-XtrhImageSnapshot {
|
||||
param([Parameter(Mandatory)][string]$CoordinatorUrl)
|
||||
return ConvertFrom-XtrhImageJson (Invoke-XtrhImageSigned -BaseUrl $CoordinatorUrl -Path '/control/snapshot')
|
||||
}
|
||||
|
||||
function Close-XtrhImageVerification {
|
||||
if ($null -ne $script:imageVerificationClient) { $script:imageVerificationClient.Dispose() }
|
||||
$script:imageVerificationCredential = ''
|
||||
}
|
||||
Reference in New Issue
Block a user