mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
Addresses 25 of 39 findings from a branch security review (assessment 5). CRITICAL: F1 gate MCP key-disclosure tools, F2 0600 keylog/pcap files, F3 per-user BaseFolder, F16 ChannelKeyMaterial IDisposable + ZeroMemory, F17 PcapFileReader MaxPacketBytes (64MB) bound, F18 FlowBuffer MaxBufferBytes (256MB) cap, F19 NodeSet export path validation, F20 ReplaySession listenPort range. HIGH: F4 IPcapAuditSink + LoggerPcapAuditSink + rate-limited frame-capture audit, F5 AES-256-GCM EncryptedKeyLogStream + SessionKeyManager (sibling .key file 0600), F14 IKeyEscrowProvider abstraction + DiskKeyEscrowProvider default (DI TryAddSingleton so consumers can route to KMS), F21 ChannelManagerOptions.MaxChannels (256) cap, F22 CaptureSessionManager.SessionFolder path-traversal guard, F23 PacketDecodeTools pcapPath/keyLogPath validation, F24 replay_pcap speed validation (NaN/Inf/<=0), F25 explicit permissions blocks on .github/workflows. MEDIUM: F6 OnTokenActivated [EditorBrowsable(Never)] + key-disclosure doc, F7 IDiagnosticsChannelMutation accessor + stack-walk caller guard (replaces internal OfflineLoadTokens), F8 PcapOptions.AllowMockClientReplay + AllowedReplayEndpoints, F9 PcapFileWriter / UaKeyLog*Writer rotation (.NNN suffix) with MaxBytes / MaxArtifactsPerSession, F13 keylog-isolation gap acknowledgment doc, F15 HashChainedAuditFileSink (HMAC-chained JSONL ledger, VerifyChain), F11 replay URL scheme allow-list (opc.tcp/opc.tcps/opc.https). LOW/INFO: F10 SBOM/CVE governance comments on PacketDotNet/SharpPcap + Docs/PacketCapture.md, F12 Security model section at top of PacketCapture.md. Tests: 100+ new NUnit tests across Audit/, Capture/, Frame/, KeyLog/, McpServerTools/, Replay/, Stack/Tcp/, DependencyInjection/. All file-mode/HMAC-chain/encryption tests are [Platform(Linux,MacOSX)]-gated where Unix-specific. Build: 0 warnings / 0 errors on Stack/Opc.Ua.Bindings.Pcap for net8.0 and net9.0. net10.0 build is environmentally blocked by a corrupted local SDK 10.0.300 workload manifest (microsoft.net.workload.mono.toolchain.net6 10.0.108 requires Emscripten.net6 >=10.0.108, only 9.0.17 installed); requires 'dotnet workload repair' from an elevated shell to validate the third TFM. No tests were run pending the SDK fix. 5 CA1508/CA2000 false positives suppressed with documented #pragma blocks (ownership transfer in escrow factory / IAsyncEnumerable iterator / unreachable catch path). Deferred to follow-up GitHub issues: 9 MEDIUM (M-SEC-07/09/10/11/12/13/16/22/31 — exception sanitization, reconnect defaults, cert-rotation races, nonce-suppress deprecation, parser hardening) and 10 LOW/INFO (L-SEC-14/15/17/23/24/33/34/35/36/37 — handler cleanup, EventSource endpoint masking, key-log schema validation, EOL deps, SAST). Full reasoning in session plan.md Round 10.
188 lines
6.2 KiB
YAML
188 lines
6.2 KiB
YAML
name: Build and Test .NET 10.0
|
|
|
|
on:
|
|
push:
|
|
branches: [ master, main, develop/* ]
|
|
pull_request:
|
|
branches: [ master, main, develop/* ]
|
|
paths:
|
|
- '**.cs'
|
|
- '**.csproj'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
discover:
|
|
name: Discover test projects
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
csprojs: ${{ steps.list.outputs.csprojs }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Enumerate Tests/Opc.Ua.*.Tests projects
|
|
id: list
|
|
shell: pwsh
|
|
run: |
|
|
# Discover test projects from the filesystem so this matrix does
|
|
# not have to be hand-maintained as projects are added/renamed.
|
|
# Projects whose name is in EXCLUDE are run by other workflows
|
|
# (AOT has its own workflow; the Stress suite is opt-in via the
|
|
# stress-test workflow).
|
|
$exclude = @('Aot', 'Stress')
|
|
$projects = Get-ChildItem -Path Tests -Directory -Filter 'Opc.Ua.*.Tests' |
|
|
Where-Object { Test-Path (Join-Path $_.FullName "$($_.Name).csproj") } |
|
|
ForEach-Object { $_.Name -replace '^Opc\.Ua\.', '' -replace '\.Tests$', '' } |
|
|
Where-Object { $exclude -notcontains $_ } |
|
|
Sort-Object
|
|
$json = ConvertTo-Json -InputObject @($projects) -Compress
|
|
Write-Host "Discovered $(@($projects).Count) project(s): $json"
|
|
"csprojs=$json" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
|
|
|
build-and-test:
|
|
needs: discover
|
|
name: test-${{matrix.os}}-${{matrix.csproj}}
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# os: [ubuntu-latest, windows-latest, macOS-latest] - disable mac os due to cost
|
|
os: [ubuntu-latest, windows-latest]
|
|
csproj: ${{ fromJSON(needs.discover.outputs.csprojs) }}
|
|
include:
|
|
- framework: 'net10.0'
|
|
dotnet-version: '10.0.x'
|
|
configuration: 'Release'
|
|
customtesttarget: net10.0
|
|
|
|
env:
|
|
OS: ${{ matrix.os }}
|
|
DOTNET_VERSION: ${{ matrix.dotnet-version }}
|
|
CONFIGURATION: ${{ matrix.configuration }}
|
|
CSPROJ: ${{ matrix.csproj }}
|
|
CSPROJECT: "./Tests/Opc.Ua.${{ matrix.csproj }}.Tests/Opc.Ua.${{ matrix.csproj }}.Tests.csproj"
|
|
TESTRESULTS: "TestResults-${{matrix.csproj}}-${{matrix.os}}-${{matrix.framework}}-${{matrix.configuration}}"
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup .NET ${{ matrix.dotnet-version }}
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{ matrix.dotnet-version }}
|
|
|
|
- name: Set Cloud Version
|
|
shell: pwsh
|
|
run: ./.azurepipelines/set-version.ps1
|
|
|
|
- name: Build
|
|
run: dotnet build ${{ env.CSPROJECT }} --framework ${{ matrix.framework }} --configuration ${{ matrix.configuration }} /p:CustomTestTarget=${{ matrix.customtesttarget }} /p:UseSharedCompilation=false -maxcpucount:1
|
|
|
|
- name: Test
|
|
# note: /p:CollectCoverage=true is only used to disable deterministic builds
|
|
shell: pwsh
|
|
run: |
|
|
$testArgs = @(
|
|
'${{ env.CSPROJECT }}',
|
|
'--no-build',
|
|
'--framework',
|
|
'${{ matrix.framework }}',
|
|
'--logger',
|
|
'trx',
|
|
'--configuration',
|
|
'${{ matrix.configuration }}',
|
|
'/p:CollectCoverage=true',
|
|
'/p:CustomTestTarget=${{ matrix.customtesttarget }}',
|
|
'--collect:XPlat Code Coverage',
|
|
'--settings',
|
|
'./Tests/coverlet.runsettings.xml',
|
|
'--results-directory',
|
|
'${{ env.TESTRESULTS }}')
|
|
|
|
if ('${{ matrix.csproj }}' -eq 'Channels.Stress')
|
|
{
|
|
$testArgs += @('--filter', '(Category=Contract|Category=Integration)&Category!=ChaosTCP&Category!=Soak')
|
|
}
|
|
|
|
dotnet test @testArgs
|
|
|
|
- name: Upload test results
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: dotnet-results-${{matrix.csproj}}-${{matrix.os}}-${{matrix.framework}}-${{matrix.configuration}}
|
|
path: ${{ env.TESTRESULTS }}
|
|
# Use always() to always run this step to publish test results when there are test failures
|
|
if: ${{ always() }}
|
|
|
|
- name: Upload to Codecov
|
|
uses: codecov/codecov-action@v6
|
|
with:
|
|
name: codecov-umbrella
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
directory: ${{ env.TESTRESULTS }}
|
|
env_vars: CSPROJ,OS,DOTNET_VERSION,CONFIGURATION
|
|
fail_ci_if_error: false
|
|
#path_to_write_report: "./coverage/codecov_report-${{matrix.os}}-${{matrix.framework}}-${{matrix.configuration}}/"
|
|
verbose: true
|
|
# Use always() to always run this step to publish test results when there are test failures
|
|
if: ${{ always() }}
|
|
|
|
aot-test:
|
|
name: aot-${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, windows-latest]
|
|
include:
|
|
- dotnet-version: '10.0.x'
|
|
configuration: 'Release'
|
|
|
|
env:
|
|
AOTPROJECT: './Tests/Opc.Ua.Aot.Tests/Opc.Ua.Aot.Tests.csproj'
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup .NET ${{ matrix.dotnet-version }}
|
|
uses: actions/setup-dotnet@v5
|
|
with:
|
|
dotnet-version: ${{ matrix.dotnet-version }}
|
|
|
|
- name: Set Cloud Version
|
|
shell: pwsh
|
|
run: ./.azurepipelines/set-version.ps1
|
|
|
|
- name: Publish NativeAOT
|
|
run: dotnet publish ${{ env.AOTPROJECT }} --configuration ${{ matrix.configuration }}
|
|
|
|
- name: Run NativeAOT Tests (Linux)
|
|
if: runner.os == 'Linux'
|
|
run: ./Tests/Opc.Ua.Aot.Tests/bin/${{ matrix.configuration }}/net10.0/linux-x64/publish/Opc.Ua.Aot.Tests
|
|
|
|
- name: Run NativeAOT Tests (Windows)
|
|
if: runner.os == 'Windows'
|
|
run: ./Tests/Opc.Ua.Aot.Tests/bin/${{ matrix.configuration }}/net10.0/win-x64/publish/Opc.Ua.Aot.Tests.exe
|
|
|
|
- name: Upload test results
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: aot-results-${{ matrix.os }}
|
|
path: '**/TestResults/**'
|
|
if: ${{ always() }}
|
|
|
|
- name: Upload HTML Test Report
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: TestReport-aot-${{ matrix.os }}
|
|
path: '**/*-report.html'
|
|
if: ${{ always() }}
|