Add signing implementation to ImageBuilder#1957
Merged
lbussell merged 55 commits intodotnet:mainfrom Feb 20, 2026
Merged
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.
Contributor
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 |
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>
…ImageSigningService class Fix constructor argument order in ImageSigningServiceTests The mock arguments for IOrasSignatureService and IPayloadSigningService were swapped, not matching the ImageSigningService constructor order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the dry-run check after image-info.json parsing, registry override application, and digest extraction. This validates pipeline configuration and logs what would be signed, matching the dry-run pattern used by CopyImageService and other commands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
932c316 to
a2fa7d1
Compare
Member
Author
|
Ready for a second look. |
Collapse the PayloadSigningService abstraction layer into ImageSigningService. PayloadSigningService was only consumed by ImageSigningService and served as an unnecessary intermediary between it and EsrpSigningService. This removes IPayloadSigningService, PayloadSigningService, and the WrittenPayload record. The payload-writing, ESRP invocation, and cert chain calculation logic now lives directly in ImageSigningService as a private SignPayloadsAsync method. Tests are updated to mock IEsrpSigningService directly instead of IPayloadSigningService, and the missing-artifact-dir test is ported from PayloadSigningServiceTests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the parameterless overload that instantiated FileSystem directly, bypassing DI. All production callers pass IFileSystem explicitly. Update CertificateChainCalculatorTests to use InMemoryFileSystem instead of writing to the real filesystem, eliminating disk I/O and the IDisposable cleanup pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Member
Author
|
I am going to combine the two new Oras interfaces as well as combine the PayloadSigningService into ImageSigningService. |
mthalman
approved these changes
Feb 20, 2026
Replace the private ExtractAllImageDigests method with the pre-existing ImageInfoHelper.GetAllDigests extension method, filtering out null/empty digests at the call site. This eliminates duplicated digest extraction logic between ImageSigningService and SignImagesCommand. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ellahathaway
approved these changes
Feb 20, 2026
mthalman
approved these changes
Feb 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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):Successful internal test run end-to-end in pipelineAdd more testsCleanup