Add signing implementation to ImageBuilder#1957
Add signing implementation to ImageBuilder#1957lbussell wants to merge 44 commits intodotnet:mainfrom
Conversation
Introduce configuration and model types for container image signing: - SigningConfiguration: holds ESRP certificate IDs for images and referrers - BuildConfiguration: holds ArtifactStagingDirectory for build artifacts - Add Signing property to PublishConfiguration - ImageSigningRequest, PayloadSigningResult, ImageSigningResult records This is Phase 1 of the signing implementation.
Introduce ORAS services using OrasProject.Oras 0.4.0 for pushing Notary v2 signatures to registries: - IOrasDescriptorService: resolves OCI descriptors from references - IOrasSignatureService: pushes signatures as referrer artifacts - OrasCredentialProviderAdapter: bridges IRegistryCredentialsProvider - Uses Packer.PackManifestAsync with Subject for referrer relationship Existing IOrasClient remains unchanged for other functionality.
Test credential mapping, null handling, and host passthrough.
Implement the core signing service layer: - IEsrpSigningService: invokes DDSignFiles.dll via MicroBuild plugin - IPayloadSigningService: writes payloads, signs via ESRP, calculates cert chain - IBulkImageSigningService: orchestrates signing and ORAS push - CertificateChainCalculator: extracts x5chain thumbprints from COSE envelopes Also adds GetEnvironmentVariable to IEnvironmentService and SigningServiceExtensions for DI registration.
Implements ISigningRequestGenerator with two methods: - GeneratePlatformSigningRequestsAsync: Converts platform digests to signing requests - GenerateManifestListSigningRequestsAsync: Converts manifest list digests to signing requests Uses LINQ to flatten the repo/image/platform hierarchy. Updates BuildCommand to use the generator instead of inline request creation.
This standalone command has been superseded by the integrated signing services that sign images immediately after build/push in BuildCommand.
- Add SignType property to SigningConfiguration (defaults to 'test') - Update EsrpSigningService to use SignType from config - Set SignType: real in publish-config-prod.yml - Set SignType: test in publish-config-nonprod.yml
- Add Enabled property to SigningConfiguration
- Update BuildCommand to check Signing.Enabled before signing
- Update publish-config templates to use $(enableSigning) variable
- Update init-imagebuilder to read enableSigning and signType from variables
To enable signing in a pipeline, set:
variables:
enableSigning: true
signType: real # or 'test'
Inject IBulkImageSigningService and call SignImagesAsync after push. Signing is skipped in dry-run mode or when SigningConfiguration is null.
Some image-info entries have null digests when their platforms weren't built in the current pipeline run. ApplyOverrideToDigest assumes non-null input, causing a NullReferenceException in the signing command. The signing request generator already filters out empty digests downstream, but the override runs first.
Extract signing logic from BuildCommand into a new standalone SignImagesCommand. This creates SignImagesCommand.cs and SignImagesOptions.cs for separate signing. Removes signing-related fields and methods from BuildCommand. Registers SignImagesCommand in ImageBuilder.
Add RegistryOverride option to SignImagesOptions and apply it to image artifact details before generating signing requests.
DDSignFiles.dll requires VSENGESRPSSL environment variable on non-Windows platforms. Add fail-fast check in EsrpSigningService to catch missing environment variables before DDSignFiles retries auth endlessly.
After ApplyRegistryOverride, the digest is already a fully qualified reference (registry/prefix/repo@sha256:...). The signing request generator was incorrectly prepending repo@ again, producing a malformed reference.
Switch from runtime-deps to runtime base image and remove --self-contained=true from dotnet publish. This provides the dotnet CLI in the container, which is needed to invoke DDSignFiles.dll for ESRP signing.
DDSignFiles expects SignFileRecordList with Certs/SrcPath/DstPath fields. We were generating the wrong schema (MicroBuild SignBatches format), causing a NullReferenceException in GetSignRecordsFromInputFile.
6d2daa9 to
e260945
Compare
Update test mocks and assertions to match the current behavior where SigningRequestGenerator passes bare digests (e.g. 'sha256:abc123') instead of full image references to the descriptor service.
There was a problem hiding this comment.
Pull request overview
This PR implements container image signing functionality for ImageBuilder using ESRP (Enterprise Signing and Release Pipeline) and ORAS (OCI Registry as Storage). The implementation replaces the previous GenerateSigningPayloadsCommand with a new end-to-end SignImagesCommand that handles payload generation, ESRP signing, certificate chain extraction, and signature artifact publishing.
Changes:
- Added comprehensive signing infrastructure with services for ESRP integration, payload signing, and ORAS-based signature publishing
- Integrated ORAS .NET library for OCI descriptor resolution and signature artifact management
- Added configuration models for signing and build artifacts
- Removed old
GenerateSigningPayloadsCommandin favor of newSignImagesCommand - Modified Dockerfile to use framework-dependent deployment instead of self-contained
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| SigningServiceExtensions.cs | DI registration extension methods for signing services |
| SigningRequestGenerator.cs | Generates signing requests by fetching OCI descriptors from registries |
| PayloadSigningService.cs | Writes payloads to disk, invokes ESRP signing, and calculates certificate chains |
| EsrpSigningService.cs | Wraps MicroBuild DDSignFiles.dll for ESRP file signing |
| CertificateChainCalculator.cs | Extracts and calculates x5chain thumbprints from COSE signature envelopes |
| BulkImageSigningService.cs | Orchestrates bulk signing and signature publishing workflow |
| OrasDotNetService.cs | ORAS .NET library integration for descriptor resolution and signature pushing |
| OrasCredentialProviderAdapter.cs | Adapts ImageBuilder credentials to ORAS authentication interface |
| IOrasSignatureService.cs, IOrasDescriptorService.cs | Interfaces for ORAS operations |
| SignImagesCommand.cs | Command implementation that orchestrates end-to-end signing process |
| SignImagesOptions.cs | CLI options for sign-images command |
| SigningConfiguration.cs, BuildConfiguration.cs | Configuration models for signing and build artifacts |
| PublishConfiguration.cs | Added Signing property |
| ConfigurationExtensions.cs | Added BuildConfiguration DI registration |
| ImageBuilder.cs | Updated DI registrations, removed old command, added new command and ORAS services |
| IEnvironmentService.cs, EnvironmentService.cs | Added GetEnvironmentVariable method |
| ImageInfoHelper.cs | Added null/empty checks before applying registry overrides |
| BuildCommand.cs | Removed unused PublishConfiguration parameter |
| Microsoft.DotNet.ImageBuilder.csproj | Added OrasProject.Oras and System.Formats.Cbor packages |
| Dockerfile.linux | Changed to framework-dependent deployment and runtime base image |
| SigningRequestGeneratorTests.cs | Tests for request generator |
| CertificateChainCalculatorTests.cs | Tests for certificate chain calculation |
| OrasDotNetServiceTests.cs | Tests for ORAS service (partial coverage) |
| OrasCredentialProviderAdapterTests.cs | Tests for credential adapter |
| GenerateSigningPayloadsCommandTests.cs | Removed old command tests |
| BuildCommandTests.cs | Updated test helper to remove unused parameter |
| ImageSigningRequest.cs, ImageSigningResult.cs, PayloadSigningResult.cs | Data transfer objects for signing operations |
| ISigningRequestGenerator.cs, IPayloadSigningService.cs, IEsrpSigningService.cs, IBulkImageSigningService.cs | Service interfaces |
| # copy everything else and publish | ||
| COPY . ./ | ||
| RUN dotnet publish -r linux-$TARGETARCH ./ImageBuilder/Microsoft.DotNet.ImageBuilder.csproj --self-contained=true --no-restore -o out | ||
| RUN dotnet publish -r linux-$TARGETARCH ./ImageBuilder/Microsoft.DotNet.ImageBuilder.csproj --no-restore -o out |
There was a problem hiding this comment.
The --self-contained=true flag has been removed from the publish command. This changes the application from self-contained to framework-dependent deployment. This requires the .NET runtime to be present in the target container, which explains the change on line 34 from runtime-deps to runtime. While this reduces image size, ensure this change is intentional and document the reason in the PR description or commit message if not already done. Also verify that this doesn't break any deployment scenarios that might rely on self-contained deployment.
Add ArgumentException/ArgumentNullException guards to GetDescriptorAsync and PushSignatureAsync. Update test to expect ArgumentNullException instead of NullReferenceException. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add ArgumentException guard for signedPayloadPath parameter. Handle nullable int? from ReadStartArray/ReadStartMap with explicit exceptions for indefinite-length CBOR structures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TODO for unused ReferrerSigningKeyCode property. Clarify Task.Run cancellation token behavior in EsrpSigningService. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce IFileSystem interface and FileSystem implementation to abstract direct System.IO calls. Refactor PayloadSigningService, EsrpSigningService, and CertificateChainCalculator to use the abstraction. Register FileSystem in DI. Existing tests unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tests for BulkImageSigningService (3 tests), PayloadSigningService (5 tests), EsrpSigningService (6 tests), SignImagesCommand (5 tests), and OrasDotNetService guard clauses (4 tests). New tests use Mock<IFileSystem> to avoid disk I/O. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@mthalman ready for super-rough first pass review, if you want to start digesting this. I have a bunch of code style things I want to go back and tidy up, but as-is it's functional. Signing validation will be a separate PR. |
# Conflicts: # src/ImageBuilder.Tests/GenerateSigningPayloadsCommandTests.cs # src/ImageBuilder/Commands/Signing/GenerateSigningPayloadsCommand.cs # src/ImageBuilder/ImageBuilder.cs
| foreach (var reference in manifestReferences) | ||
| { | ||
| _logger.LogInformation(" Manifest reference: {Reference}", reference); | ||
| var request = await CreateSigningRequestAsync(reference, cancellationToken); | ||
| requests.Add(request); | ||
| } |
There was a problem hiding this comment.
Could this be done in parallel?
There was a problem hiding this comment.
Definitely, I'll do that.
|
|
||
| if (Options.IsDryRun) | ||
| { | ||
| logger.LogInformation("Dry run enabled. Skipping actual signing."); |
There was a problem hiding this comment.
Normally I like to try to execute as much as possible in dry run. That's just extra validation that the code works (or at least doesn't crash) in PRs. How far can we get in execution without actually signing?
There was a problem hiding this comment.
I'm taking a look at expanding the scope of dry-run.
…ImageSigningService class
Related:
What's included in this PR:
Summary of types added
Signing
ImageSigningRequestPayloadSigningResultImageSigningResultBulkImageSigningService : IBulkImageSigningService- orchestrates signing and pushing signaturesEsrpSigningService : IEsrpSigningService- invokes DDSignFiles.dll via MicroBuildPayloadSigningService : IPayloadSigningService- writes payloads, signs via ESRP, calculates cert chainsSigningRequestGenerator : ISigningRequestGenerator- creates requests from ImageArtifactDetailsCertificateChainCalculator- static class to extract x5chain from COSE envelopesSigningServiceExtensions- DI registration extension methodsOras
OrasCredentialProviderAdapter- adapts ImageBuilder credentials to ORAS authOrasDotNetService : IOrasDescriptorService, IOrasSignatureService- ORAS .NET library implementationConfiguration
BuildConfiguration- for build/pipeline artifact settingsSigningConfiguration- for ESRP signing settingsTests
OrasCredentialProviderAdapterTestsSigningRequestGeneratorTestsWhat's not included in this PR:
Integration into Build or Post-Build commands or pipeline stepsIt's a separate command, now follows original design outlined in the issue.Comprehensive testsAdded.TODO (before leaving draft mode):