diff --git a/internal/builder/vm/lcow/sandbox_options.go b/internal/builder/vm/lcow/sandbox_options.go index b6cc22f94b..492d3678b5 100644 --- a/internal/builder/vm/lcow/sandbox_options.go +++ b/internal/builder/vm/lcow/sandbox_options.go @@ -38,4 +38,7 @@ type ConfidentialConfig struct { // UvmReferenceInfoFile is the path to the signed UVM reference info file for attestation. UvmReferenceInfoFile string + + // UvmHashEnvelopeReferenceInfoFile is the path to the hash envelope signed UVM reference info file for attestation. + UvmHashEnvelopeReferenceInfoFile string } diff --git a/internal/builder/vm/lcow/specs.go b/internal/builder/vm/lcow/specs.go index 10e6b9363d..2207847b5e 100644 --- a/internal/builder/vm/lcow/specs.go +++ b/internal/builder/vm/lcow/specs.go @@ -334,9 +334,10 @@ func parseSandboxOptions(ctx context.Context, platform string, annotations map[s noSecurityHardware := oci.ParseAnnotationsBool(ctx, annotations, shimannotations.NoSecurityHardware, false) if securityPolicy != "" && !noSecurityHardware { sandboxOptions.ConfidentialConfig = &ConfidentialConfig{ - SecurityPolicy: securityPolicy, - SecurityPolicyEnforcer: oci.ParseAnnotationsString(annotations, shimannotations.LCOWSecurityPolicyEnforcer, ""), - UvmReferenceInfoFile: oci.ParseAnnotationsString(annotations, shimannotations.LCOWReferenceInfoFile, vmutils.DefaultUVMReferenceInfoFile), + SecurityPolicy: securityPolicy, + SecurityPolicyEnforcer: oci.ParseAnnotationsString(annotations, shimannotations.LCOWSecurityPolicyEnforcer, ""), + UvmReferenceInfoFile: oci.ParseAnnotationsString(annotations, shimannotations.LCOWReferenceInfoFile, vmutils.DefaultUVMReferenceInfoFile), + UvmHashEnvelopeReferenceInfoFile: oci.ParseAnnotationsString(annotations, shimannotations.UVMHashEnvelopeReferenceInfoFile, vmutils.DefaultUVMHashEnvelopeReferenceInfoFile), } log.G(ctx).WithFields(logrus.Fields{ diff --git a/internal/controller/vm/vm_lcow.go b/internal/controller/vm/vm_lcow.go index 0775ff1377..a200dbe5f6 100644 --- a/internal/controller/vm/vm_lcow.go +++ b/internal/controller/vm/vm_lcow.go @@ -58,19 +58,31 @@ func (c *Controller) buildConfidentialOptions(ctx context.Context) (*guestresour return nil, nil } + bootFilesPath := vmutils.DefaultLCOWOSBootFilesPath() + uvmReferenceInfoEncoded, err := vmutils.ParseUVMReferenceInfo( ctx, - vmutils.DefaultLCOWOSBootFilesPath(), + bootFilesPath, c.sandboxOptions.ConfidentialConfig.UvmReferenceInfoFile, ) if err != nil { return nil, fmt.Errorf("failed to parse UVM reference info: %w", err) } + uvmHashEnvelopeReferenceInfoEncoded, err := vmutils.ParseUVMReferenceInfo( + ctx, + bootFilesPath, + c.sandboxOptions.ConfidentialConfig.UvmHashEnvelopeReferenceInfoFile, + ) + if err != nil { + return nil, fmt.Errorf("failed to parse UVM hash envelope reference info: %w", err) + } + return &guestresource.ConfidentialOptions{ - EnforcerType: c.sandboxOptions.ConfidentialConfig.SecurityPolicyEnforcer, - EncodedSecurityPolicy: c.sandboxOptions.ConfidentialConfig.SecurityPolicy, - EncodedUVMReference: uvmReferenceInfoEncoded, + EnforcerType: c.sandboxOptions.ConfidentialConfig.SecurityPolicyEnforcer, + EncodedSecurityPolicy: c.sandboxOptions.ConfidentialConfig.SecurityPolicy, + EncodedUVMReference: uvmReferenceInfoEncoded, + EncodedUVMHashEnvelopeReference: uvmHashEnvelopeReferenceInfoEncoded, }, nil } diff --git a/internal/gcs-sidecar/handlers.go b/internal/gcs-sidecar/handlers.go index a51c3fd8f5..f5a7c48d5e 100644 --- a/internal/gcs-sidecar/handlers.go +++ b/internal/gcs-sidecar/handlers.go @@ -664,7 +664,8 @@ func (b *Bridge) modifySettings(req *request) (err error) { err := b.hostState.securityOptions.SetConfidentialOptions(ctx, securityPolicyRequest.EnforcerType, securityPolicyRequest.EncodedSecurityPolicy, - securityPolicyRequest.EncodedUVMReference) + securityPolicyRequest.EncodedUVMReference, + securityPolicyRequest.EncodedUVMHashEnvelopeReference) if err != nil { return errors.Wrap(err, "Failed to set Confidentia UVM Options") } diff --git a/internal/gcs-sidecar/host.go b/internal/gcs-sidecar/host.go index 2c73f34ff3..ebe4f5687e 100644 --- a/internal/gcs-sidecar/host.go +++ b/internal/gcs-sidecar/host.go @@ -52,6 +52,7 @@ func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer, logWriter io initialEnforcer, false, "", + "", logWriter, ) return &Host{ diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index 4cd8a99d3c..1b95a9070f 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -123,6 +123,7 @@ func NewHost(rtime runtime.Runtime, vsock transport.Transport, initialEnforcer s initialEnforcer, false, "", + "", logWriter, ) return &Host{ @@ -805,7 +806,8 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, req * return h.securityOptions.SetConfidentialOptions(ctx, r.EnforcerType, r.EncodedSecurityPolicy, - r.EncodedUVMReference) + r.EncodedUVMReference, + r.EncodedUVMHashEnvelopeReference) case guestresource.ResourceTypePolicyFragment: r, ok := req.Settings.(*guestresource.SecurityPolicyFragment) if !ok { diff --git a/internal/oci/uvm.go b/internal/oci/uvm.go index 5bfa68f7f0..0725ac73a4 100644 --- a/internal/oci/uvm.go +++ b/internal/oci/uvm.go @@ -250,6 +250,7 @@ func handleWCOWSecurityPolicy(ctx context.Context, a map[string]string, wopts *u wopts.DisableSecureBoot = ParseAnnotationsBool(ctx, a, annotations.WCOWDisableSecureBoot, false) wopts.GuestStateFilePath = ParseAnnotationsString(a, annotations.WCOWGuestStateFile, uvm.GetDefaultConfidentialVMGSPath()) wopts.UVMReferenceInfoFile = ParseAnnotationsString(a, annotations.WCOWReferenceInfoFile, uvm.GetDefaultReferenceInfoFilePath()) + wopts.UVMHashEnvelopeReferenceInfoFile = ParseAnnotationsString(a, annotations.UVMHashEnvelopeReferenceInfoFile, uvm.GetDefaultHashEnvelopeReferenceInfoFilePath()) wopts.IsolationType = "SecureNestedPaging" if noSecurityHardware := ParseAnnotationsBool(ctx, a, annotations.NoSecurityHardware, false); noSecurityHardware { wopts.IsolationType = "GuestStateOnly" @@ -377,6 +378,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( lopts.SecurityPolicy = ParseAnnotationsString(s.Annotations, annotations.LCOWSecurityPolicy, lopts.SecurityPolicy) lopts.SecurityPolicyEnforcer = ParseAnnotationsString(s.Annotations, annotations.LCOWSecurityPolicyEnforcer, lopts.SecurityPolicyEnforcer) lopts.UVMReferenceInfoFile = ParseAnnotationsString(s.Annotations, annotations.LCOWReferenceInfoFile, lopts.UVMReferenceInfoFile) + lopts.UVMHashEnvelopeReferenceInfoFile = ParseAnnotationsString(s.Annotations, annotations.UVMHashEnvelopeReferenceInfoFile, lopts.UVMHashEnvelopeReferenceInfoFile) lopts.KernelBootOptions = ParseAnnotationsString(s.Annotations, annotations.KernelBootOptions, lopts.KernelBootOptions) lopts.DisableTimeSyncService = ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableLCOWTimeSyncService, lopts.DisableTimeSyncService) lopts.WritableOverlayDirs = ParseAnnotationsBool(ctx, s.Annotations, iannotations.WritableOverlayDirs, lopts.WritableOverlayDirs) diff --git a/internal/protocol/guestresource/resources.go b/internal/protocol/guestresource/resources.go index 8a58949281..7d5988d930 100644 --- a/internal/protocol/guestresource/resources.go +++ b/internal/protocol/guestresource/resources.go @@ -232,9 +232,10 @@ type SignalProcessOptionsWCOW struct { // ConfidentialOptions is used to set various confidential container specific // options. type ConfidentialOptions struct { - EnforcerType string `json:"EnforcerType,omitempty"` - EncodedSecurityPolicy string `json:"EncodedSecurityPolicy,omitempty"` - EncodedUVMReference string `json:"EncodedUVMReference,omitempty"` + EnforcerType string `json:"EnforcerType,omitempty"` + EncodedSecurityPolicy string `json:"EncodedSecurityPolicy,omitempty"` + EncodedUVMReference string `json:"EncodedUVMReference,omitempty"` + EncodedUVMHashEnvelopeReference string `json:"EncodedUVMHashEnvelopeReference,omitempty"` } type SecurityPolicyFragment struct { diff --git a/internal/uvm/create.go b/internal/uvm/create.go index 65ce81901b..a219eb26eb 100644 --- a/internal/uvm/create.go +++ b/internal/uvm/create.go @@ -133,12 +133,13 @@ type Options struct { } type ConfidentialCommonOptions struct { - GuestStateFilePath string // The vmgs file path to load - SecurityPolicy string // Optional security policy - SecurityPolicyEnabled bool // Set when there is a security policy to apply on actual SNP hardware, use this rathen than checking the string length - SecurityPolicyEnforcer string // Set which security policy enforcer to use (open door or rego). This allows for better fallback mechanic. - UVMReferenceInfoFile string // Path to the file that contains the signed UVM measurements - BundleDirectory string // This allows paths to be constructed relative to a per-VM bundle directory. + GuestStateFilePath string // The vmgs file path to load + SecurityPolicy string // Optional security policy + SecurityPolicyEnabled bool // Set when there is a security policy to apply on actual SNP hardware, use this rathen than checking the string length + SecurityPolicyEnforcer string // Set which security policy enforcer to use (open door or rego). This allows for better fallback mechanic. + UVMReferenceInfoFile string // Path to the file that contains the signed UVM measurements + UVMHashEnvelopeReferenceInfoFile string // Path to the file that contains the hash envelope signed UVM measurements + BundleDirectory string // This allows paths to be constructed relative to a per-VM bundle directory. } func verifyWCOWBootFiles(bootFiles *WCOWBootFiles) error { diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index c6e9a52ffc..e4cf5ae9ac 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -143,8 +143,9 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW { DisableTimeSyncService: false, ConfidentialLCOWOptions: &ConfidentialLCOWOptions{ ConfidentialCommonOptions: &ConfidentialCommonOptions{ - SecurityPolicyEnabled: false, - UVMReferenceInfoFile: vmutils.DefaultUVMReferenceInfoFile, + SecurityPolicyEnabled: false, + UVMReferenceInfoFile: vmutils.DefaultUVMReferenceInfoFile, + UVMHashEnvelopeReferenceInfoFile: vmutils.DefaultUVMHashEnvelopeReferenceInfoFile, }, }, } diff --git a/internal/uvm/create_wcow.go b/internal/uvm/create_wcow.go index b37348132e..188201ec56 100644 --- a/internal/uvm/create_wcow.go +++ b/internal/uvm/create_wcow.go @@ -93,7 +93,11 @@ func GetDefaultConfidentialEFIPath() string { } func GetDefaultReferenceInfoFilePath() string { - return filepath.Join(defaultConfidentialWCOWOSBootFilesPath(), "reference_info.cose") + return filepath.Join(defaultConfidentialWCOWOSBootFilesPath(), vmutils.DefaultUVMReferenceInfoFile) +} + +func GetDefaultHashEnvelopeReferenceInfoFilePath() string { + return filepath.Join(defaultConfidentialWCOWOSBootFilesPath(), vmutils.DefaultUVMHashEnvelopeReferenceInfoFile) } // NewDefaultOptionsWCOW creates the default options for a bootable version of diff --git a/internal/uvm/security_policy.go b/internal/uvm/security_policy.go index 3fa47e87b1..5778919b9d 100644 --- a/internal/uvm/security_policy.go +++ b/internal/uvm/security_policy.go @@ -67,6 +67,28 @@ func WithUVMReferenceInfo(referenceRoot string, referenceName string) Confidenti } } +// WithUVMHashEnvelopeReferenceInfo reads UVM hash envelope reference info file +// and base64 encodes the content before setting it for the resource. This is +// no-op if the `referenceName` is empty or the file doesn't exist. +func WithUVMHashEnvelopeReferenceInfo(referenceRoot string, referenceName string) ConfidentialUVMOpt { + return func(ctx context.Context, r *guestresource.ConfidentialOptions) error { + if referenceName == "" { + return nil + } + fullFilePath := filepath.Join(referenceRoot, referenceName) + encoded, err := base64EncodeFileContents(fullFilePath) + if err != nil { + if os.IsNotExist(err) { + log.G(ctx).WithField("filePath", fullFilePath).Debug("UVM hash envelope reference info file not found") + return nil + } + return fmt.Errorf("failed to read UVM hash envelope reference info file: %w", err) + } + r.EncodedUVMHashEnvelopeReference = encoded + return nil + } +} + // SetConfidentialUVMOptions sends information required to run the UVM on // SNP hardware, e.g., security policy and enforcer type, signed UVM reference // information, etc. diff --git a/internal/uvm/start.go b/internal/uvm/start.go index e429fcc4a0..300cd63b9d 100644 --- a/internal/uvm/start.go +++ b/internal/uvm/start.go @@ -265,21 +265,24 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) { uvm.SCSIManager = mgr if uvm.HasConfidentialPolicy() { - var policy, enforcer, referenceInfoFileRoot, referenceInfoFilePath string + var policy, enforcer, referenceInfoFileRoot, referenceInfoFilePath, hashEnvelopeReferenceInfoFilePath string if uvm.OS() == "linux" { policy = uvm.createOpts.(*OptionsLCOW).SecurityPolicy enforcer = uvm.createOpts.(*OptionsLCOW).SecurityPolicyEnforcer referenceInfoFilePath = uvm.createOpts.(*OptionsLCOW).UVMReferenceInfoFile + hashEnvelopeReferenceInfoFilePath = uvm.createOpts.(*OptionsLCOW).UVMHashEnvelopeReferenceInfoFile referenceInfoFileRoot = vmutils.DefaultLCOWOSBootFilesPath() } else if uvm.OS() == "windows" { policy = uvm.createOpts.(*OptionsWCOW).SecurityPolicy enforcer = uvm.createOpts.(*OptionsWCOW).SecurityPolicyEnforcer referenceInfoFilePath = uvm.createOpts.(*OptionsWCOW).UVMReferenceInfoFile + hashEnvelopeReferenceInfoFilePath = uvm.createOpts.(*OptionsWCOW).UVMHashEnvelopeReferenceInfoFile } copts := []ConfidentialUVMOpt{ WithSecurityPolicy(policy), WithSecurityPolicyEnforcer(enforcer), WithUVMReferenceInfo(referenceInfoFileRoot, referenceInfoFilePath), + WithUVMHashEnvelopeReferenceInfo(referenceInfoFileRoot, hashEnvelopeReferenceInfoFilePath), } if err := uvm.SetConfidentialUVMOptions(ctx, copts...); err != nil { return err diff --git a/internal/vm/vmutils/constants.go b/internal/vm/vmutils/constants.go index a332fb6a99..b86678d173 100644 --- a/internal/vm/vmutils/constants.go +++ b/internal/vm/vmutils/constants.go @@ -35,6 +35,10 @@ const ( // UVM info file, which can be made available to workload containers and used for // validation purposes. DefaultUVMReferenceInfoFile = "reference_info.cose" + // DefaultUVMHashEnvelopeReferenceInfoFile is the default file name for a COSE_Sign1 + // hash envelope reference UVM info file, which can be made available to workload + // containers and used for validation purposes. + DefaultUVMHashEnvelopeReferenceInfoFile = "reference_info_hash_envelope.cose" // InitrdFile is the default file name for an initrd.img used to boot LCOW. InitrdFile = "initrd.img" diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 2b469134fe..80f0264569 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -312,6 +312,12 @@ const ( VirtualMachineKernelDrivers = "io.microsoft.virtualmachine.kerneldrivers" ) +// Confidential UVM annotations. +const ( + // UVMHashEnvelopeReferenceInfoFile specifies the filename of a hash envelope signed UVM reference file to be passed to UVM. + UVMHashEnvelopeReferenceInfoFile = "io.microsoft.virtualmachine.uvm-hash-envelope-reference-info-file" +) + // uVM CPU annotations. const ( // CPUGroupID specifies the cpugroup ID that a UVM should be assigned to, if any. diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index 5b05160492..ff320bc852 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -47,10 +47,11 @@ const ( const plan9Prefix = "plan9://" const ( - SecurityContextDirTemplate = "security-context-*" - PolicyFilename = "security-policy-base64" - HostAMDCertFilename = "host-amd-cert-base64" - ReferenceInfoFilename = "reference-info-base64" + SecurityContextDirTemplate = "security-context-*" + PolicyFilename = "security-policy-base64" + HostAMDCertFilename = "host-amd-cert-base64" + ReferenceInfoFilename = "reference-info-base64" + HashEnvelopeReferenceInfoFilename = "transparent-reference-info-base64" ) // PolicyConfig contains toml or JSON config for security policy. diff --git a/pkg/securitypolicy/securitypolicy_options.go b/pkg/securitypolicy/securitypolicy_options.go index 35dd755367..cf993780cd 100644 --- a/pkg/securitypolicy/securitypolicy_options.go +++ b/pkg/securitypolicy/securitypolicy_options.go @@ -24,19 +24,21 @@ import ( type SecurityOptions struct { // state required for the security policy enforcement - PolicyEnforcer SecurityPolicyEnforcer - PolicyEnforcerSet bool - UvmReferenceInfo string - policyMutex sync.Mutex - logWriter io.Writer + PolicyEnforcer SecurityPolicyEnforcer + PolicyEnforcerSet bool + UvmReferenceInfo string + UvmHashEnvelopeReferenceInfo string + policyMutex sync.Mutex + logWriter io.Writer } -func NewSecurityOptions(enforcer SecurityPolicyEnforcer, enforcerSet bool, uvmReferenceInfo string, logWriter io.Writer) *SecurityOptions { +func NewSecurityOptions(enforcer SecurityPolicyEnforcer, enforcerSet bool, uvmReferenceInfo string, uvmHashEnvelopeReferenceInfo string, logWriter io.Writer) *SecurityOptions { return &SecurityOptions{ - PolicyEnforcer: enforcer, - PolicyEnforcerSet: enforcerSet, - UvmReferenceInfo: uvmReferenceInfo, - logWriter: logWriter, + PolicyEnforcer: enforcer, + PolicyEnforcerSet: enforcerSet, + UvmReferenceInfo: uvmReferenceInfo, + UvmHashEnvelopeReferenceInfo: uvmHashEnvelopeReferenceInfo, + logWriter: logWriter, } } @@ -46,7 +48,7 @@ func NewSecurityOptions(enforcer SecurityPolicyEnforcer, enforcerSet bool, uvmRe // encoded security policy and signed UVM reference information The security // policy and uvm reference information can be further presented to workload // containers for validation and attestation purposes. -func (s *SecurityOptions) SetConfidentialOptions(ctx context.Context, enforcerType string, encodedSecurityPolicy string, encodedUVMReference string) error { +func (s *SecurityOptions) SetConfidentialOptions(ctx context.Context, enforcerType string, encodedSecurityPolicy string, encodedUVMReference string, encodedUVMHashEnvelopeReference string) error { s.policyMutex.Lock() defer s.policyMutex.Unlock() @@ -95,6 +97,7 @@ func (s *SecurityOptions) SetConfidentialOptions(ctx context.Context, enforcerTy s.PolicyEnforcer = p s.PolicyEnforcerSet = true s.UvmReferenceInfo = encodedUVMReference + s.UvmHashEnvelopeReferenceInfo = encodedUVMHashEnvelopeReference return nil } @@ -191,7 +194,7 @@ func writeFileInDir(dir string, filename string, data []byte, perm os.FileMode) func (s *SecurityOptions) WriteSecurityContextDir(spec *specs.Spec) error { encodedPolicy := s.PolicyEnforcer.EncodedSecurityPolicy() hostAMDCert := spec.Annotations[annotations.WCOWHostAMDCertificate] - if len(encodedPolicy) > 0 || len(hostAMDCert) > 0 || len(s.UvmReferenceInfo) > 0 { + if len(encodedPolicy) > 0 || len(hostAMDCert) > 0 || len(s.UvmReferenceInfo) > 0 || len(s.UvmHashEnvelopeReferenceInfo) > 0 { // Use os.MkdirTemp to make sure that the directory is unique. securityContextDir, err := os.MkdirTemp(spec.Root.Path, SecurityContextDirTemplate) if err != nil { @@ -212,6 +215,11 @@ func (s *SecurityOptions) WriteSecurityContextDir(spec *specs.Spec) error { return fmt.Errorf("failed to write UVM reference info: %w", err) } } + if len(s.UvmHashEnvelopeReferenceInfo) > 0 { + if err := writeFileInDir(securityContextDir, HashEnvelopeReferenceInfoFilename, []byte(s.UvmHashEnvelopeReferenceInfo), 0777); err != nil { + return fmt.Errorf("failed to write UVM hash envelope reference info: %w", err) + } + } if len(hostAMDCert) > 0 { if err := writeFileInDir(securityContextDir, HostAMDCertFilename, []byte(hostAMDCert), 0777); err != nil { diff --git a/test/gcs/main_test.go b/test/gcs/main_test.go index 0875102005..5abbeea2c0 100644 --- a/test/gcs/main_test.go +++ b/test/gcs/main_test.go @@ -180,7 +180,7 @@ func getHostErr(rt runtime.Runtime, tp transport.Transport) (*hcsv2.Host, error) h := hcsv2.NewHost(rt, tp, &securitypolicy.OpenDoorSecurityPolicyEnforcer{}, os.Stdout) if err := h.SecurityOptions().SetConfidentialOptions( context.Background(), - "", "", "", + "", "", "", "", ); err != nil { return nil, fmt.Errorf("could not set host security policy: %w", err) }