diff --git a/guest/prot/protocol.go b/guest/prot/protocol.go index 70bbe8ecc7..06258206e0 100644 --- a/guest/prot/protocol.go +++ b/guest/prot/protocol.go @@ -578,9 +578,9 @@ func UnmarshalContainerModifySettings(b []byte) (*ContainerModifySettings, error } msr.Settings = cc case guestresource.ResourceTypeSecurityPolicy: - enforcer := &guestresource.LCOWSecurityPolicyEnforcer{} + enforcer := &guestresource.LCOWConfidentialOptions{} if err := commonutils.UnmarshalJSONWithHresult(msrRawSettings, enforcer); err != nil { - return &request, errors.Wrap(err, "failed to unmarshal settings as LCOWSecurityPolicyEnforcer") + return &request, errors.Wrap(err, "failed to unmarshal settings as LCOWConfidentialOptions") } msr.Settings = enforcer default: diff --git a/guest/runtime/hcsv2/uvm.go b/guest/runtime/hcsv2/uvm.go index 28a9d8e1ea..7f001cb2c9 100644 --- a/guest/runtime/hcsv2/uvm.go +++ b/guest/runtime/hcsv2/uvm.go @@ -59,6 +59,7 @@ type Host struct { policyMutex sync.Mutex securityPolicyEnforcer securitypolicy.SecurityPolicyEnforcer securityPolicyEnforcerSet bool + uvmReferenceInfo string } func NewHost(rtime runtime.Runtime, vsock transport.Transport) *Host { @@ -72,14 +73,17 @@ func NewHost(rtime runtime.Runtime, vsock transport.Transport) *Host { } } -// SetSecurityPolicy takes a base64 encoded security policy -// and sets up our internal data structures we use to store -// said policy. +// SetConfidentialUVMOptions takes a security policy enforcer type, base64 +// encoded security policy and base64 encoded UVM reference information to set +// up our internal data structures we use to store said policy. The signed +// UVM measurement can be presented to the workload containers via an +// environment variable if client requests so. +// // The security policy is transmitted as json in an annotation, // so we first have to remove the base64 encoding that allows // the JSON based policy to be passed as a string. From there, // we decode the JSON and setup our security policy state -func (h *Host) SetSecurityPolicy(enforcerType, base64EncodedPolicy string) error { +func (h *Host) SetConfidentialUVMOptions(enforcerType string, base64EncodedPolicy string, base64UVMReference string) error { h.policyMutex.Lock() defer h.policyMutex.Unlock() if h.securityPolicyEnforcerSet { @@ -107,6 +111,7 @@ func (h *Host) SetSecurityPolicy(enforcerType, base64EncodedPolicy string) error h.securityPolicyEnforcer = p h.securityPolicyEnforcerSet = true + h.uvmReferenceInfo = base64UVMReference return nil } @@ -289,16 +294,20 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM return nil, errors.Wrapf(err, "container creation denied due to policy") } - // Export security policy as one of the process's environment variables so that application and sidecar - // containers can have access to it. The security policy is required by containers which need to extract - // init-time claims found in the security policy. + // Export security policy and signed UVM reference info as one of the + // process's environment variables so that application and sidecar + // containers can have access to it. The security policy is required + // by containers which need to extract init-time claims found in the + // security policy. // - // We append the variable after the security policy enforcing logic completes to bypass it; the - // security policy variable cannot be included in the security policy as its value is not available - // security policy construction time. + // We append the variable after the security policy enforcing logic + // completes to bypass it; the security policy variable cannot be included + // in the security policy as its value is not available security policy + // construction time. if oci.ParseAnnotationsBool(ctx, settings.OCISpecification.Annotations, annotations.SecurityPolicyEnv, false) { secPolicyEnv := fmt.Sprintf("SECURITY_POLICY=%s", h.securityPolicyEnforcer.EncodedSecurityPolicy()) - settings.OCISpecification.Process.Env = append(settings.OCISpecification.Process.Env, secPolicyEnv) + uvmReferenceInfo := fmt.Sprintf("HCSSHIM_UVM_REFERENCE_INFO=%s", h.uvmReferenceInfo) + settings.OCISpecification.Process.Env = append(settings.OCISpecification.Process.Env, secPolicyEnv, uvmReferenceInfo) } // Create the BundlePath @@ -373,11 +382,11 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, req * } return c.modifyContainerConstraints(ctx, req.RequestType, req.Settings.(*guestresource.LCOWContainerConstraints)) case guestresource.ResourceTypeSecurityPolicy: - r, ok := req.Settings.(*guestresource.LCOWSecurityPolicyEnforcer) + r, ok := req.Settings.(*guestresource.LCOWConfidentialOptions) if !ok { - return errors.New("the request's settings are not of type LCOWSecurityPolicyEnforcer") + return errors.New("the request's settings are not of type LCOWConfidentialOptions") } - return h.SetSecurityPolicy(r.EnforcerType, r.EncodedSecurityPolicy) + return h.SetConfidentialUVMOptions(r.EnforcerType, r.EncodedSecurityPolicy, r.EncodedUVMReference) default: return errors.Errorf("the ResourceType \"%s\" is not supported for UVM", req.ResourceType) } diff --git a/oci/uvm.go b/oci/uvm.go index 34b1d0541e..e2fc140ae3 100644 --- a/oci/uvm.go +++ b/oci/uvm.go @@ -278,6 +278,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( lopts.EnableScratchEncryption = ParseAnnotationsBool(ctx, s.Annotations, annotations.EncryptedScratchDisk, lopts.EnableScratchEncryption) lopts.SecurityPolicy = parseAnnotationsString(s.Annotations, annotations.SecurityPolicy, lopts.SecurityPolicy) lopts.SecurityPolicyEnforcer = parseAnnotationsString(s.Annotations, annotations.SecurityPolicyEnforcer, lopts.SecurityPolicyEnforcer) + lopts.UVMReferenceInfoFile = parseAnnotationsString(s.Annotations, annotations.UVMReferenceInfoFile, lopts.UVMReferenceInfoFile) lopts.KernelBootOptions = parseAnnotationsString(s.Annotations, annotations.KernelBootOptions, lopts.KernelBootOptions) lopts.DisableTimeSyncService = ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableLCOWTimeSyncService, lopts.DisableTimeSyncService) handleAnnotationPreferredRootFSType(ctx, s.Annotations, lopts) diff --git a/protocol/guestresource/resources.go b/protocol/guestresource/resources.go index 1ab7d30a67..799864c9bf 100644 --- a/protocol/guestresource/resources.go +++ b/protocol/guestresource/resources.go @@ -159,7 +159,8 @@ type SignalProcessOptionsWCOW struct { Signal guestrequest.SignalValueWCOW `json:",omitempty"` } -type LCOWSecurityPolicyEnforcer struct { +type LCOWConfidentialOptions struct { EnforcerType string `json:"EnforcerType,omitempty"` EncodedSecurityPolicy string `json:"EncodedSecurityPolicy,omitempty"` + EncodedUVMReference string `json:"EncodedUVMReference,omitempty"` } diff --git a/tools/uvmboot/lcow.go b/tools/uvmboot/lcow.go index 8d6b0b187b..fbe81a1579 100644 --- a/tools/uvmboot/lcow.go +++ b/tools/uvmboot/lcow.go @@ -268,7 +268,7 @@ func runLCOW(ctx context.Context, options *uvm.OptionsLCOW, c *cli.Context) erro return err } - if err := vm.SetSecurityPolicy(ctx, "", options.SecurityPolicy); err != nil { + if err := vm.SetConfidentialUVMOptions(ctx, uvm.WithSecurityPolicy(options.SecurityPolicy)); err != nil { return fmt.Errorf("could not set UVM security policy: %w", err) } diff --git a/uvm/create_lcow.go b/uvm/create_lcow.go index 3f1c776fc8..c0946d3dee 100644 --- a/uvm/create_lcow.go +++ b/uvm/create_lcow.go @@ -74,8 +74,13 @@ const ( // UncompressedKernelFile is the default file name for an uncompressed // kernel used to boot LCOW with KernelDirect. UncompressedKernelFile = "vmlinux" - // In the SNP case both the kernel (bzImage) and initrd are stored in a vmgs (VM Guest State) file + // GuestStateFile is the default file name for a vmgs (VM Guest State) file + // which combines kernel and initrd and is used to boot from in the SNP case. GuestStateFile = "kernelinitrd.vmgs" + // UVMReferenceInfoFile is the default file name for a COSE_Sign1 + // reference UVM info, which can be made available to workload containers + // and can be used for validation purposes. + UVMReferenceInfoFile = "reference_info.cose" ) // OptionsLCOW are the set of options passed to CreateLCOW() to create a utility vm. @@ -104,6 +109,7 @@ type OptionsLCOW struct { 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, standard or rego). This allows for better fallback mechanic. + UVMReferenceInfoFile string // Filename under `BootFilesPath` for (potentially signed) UVM image reference information. UseGuestStateFile bool // Use a vmgs file that contains a kernel and initrd, required for SNP GuestStateFile string // The vmgs file to load DisableTimeSyncService bool // Disables the time synchronization service @@ -155,6 +161,7 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW { SecurityPolicyEnabled: false, SecurityPolicyEnforcer: "", SecurityPolicy: "", + UVMReferenceInfoFile: UVMReferenceInfoFile, GuestStateFile: "", DisableTimeSyncService: false, } diff --git a/uvm/security_policy.go b/uvm/security_policy.go index 6e6f1d93ba..531068ca90 100644 --- a/uvm/security_policy.go +++ b/uvm/security_policy.go @@ -4,7 +4,10 @@ package uvm import ( "context" + "encoding/base64" "fmt" + "os" + "path/filepath" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" @@ -12,37 +15,75 @@ import ( "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) -// SetSecurityPolicy tells the gcs instance in the UVM what policy to apply. +type ConfidentialUVMOpt func(ctx context.Context, r *guestresource.LCOWConfidentialOptions) error + +// WithSecurityPolicy sets the desired security policy for the resource. +func WithSecurityPolicy(policy string) ConfidentialUVMOpt { + return func(ctx context.Context, r *guestresource.LCOWConfidentialOptions) error { + if policy == "" { + openDoorPolicy := securitypolicy.NewOpenDoorPolicy() + policyString, err := openDoorPolicy.EncodeToString() + if err != nil { + return err + } + policy = policyString + } + r.EncodedSecurityPolicy = policy + return nil + } +} + +// WithSecurityPolicyEnforcer sets the desired enforcer type for the resource. +func WithSecurityPolicyEnforcer(enforcer string) ConfidentialUVMOpt { + return func(ctx context.Context, r *guestresource.LCOWConfidentialOptions) error { + r.EnforcerType = enforcer + return nil + } +} + +// WithUVMReferenceInfo reads UVM 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 WithUVMReferenceInfo(referenceRoot string, referenceName string) ConfidentialUVMOpt { + return func(ctx context.Context, r *guestresource.LCOWConfidentialOptions) error { + if referenceName == "" { + return nil + } + content, err := os.ReadFile(filepath.Join(referenceRoot, referenceName)) + if err != nil && !os.IsNotExist(err) { + return err + } + r.EncodedUVMReference = base64.StdEncoding.EncodeToString(content) + 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. // // This has to happen before we start mounting things or generally changing // the state of the UVM after is has been measured at startup -func (uvm *UtilityVM) SetSecurityPolicy(ctx context.Context, enforcer, policy string) error { +func (uvm *UtilityVM) SetConfidentialUVMOptions(ctx context.Context, opts ...ConfidentialUVMOpt) error { if uvm.operatingSystem != "linux" { return errNotSupported } - if policy == "" { - openDoorPolicy := securitypolicy.NewOpenDoorPolicy() - policyString, err := openDoorPolicy.EncodeToString() - if err != nil { - return err - } - policy = policyString - } - uvm.m.Lock() defer uvm.m.Unlock() + confOpts := &guestresource.LCOWConfidentialOptions{} + for _, o := range opts { + if err := o(ctx, confOpts); err != nil { + return err + } + } modification := &hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeAdd, - } - - modification.GuestRequest = guestrequest.ModificationRequest{ - ResourceType: guestresource.ResourceTypeSecurityPolicy, - RequestType: guestrequest.RequestTypeAdd, - Settings: guestresource.LCOWSecurityPolicyEnforcer{ - EnforcerType: enforcer, - EncodedSecurityPolicy: policy, + GuestRequest: guestrequest.ModificationRequest{ + ResourceType: guestresource.ResourceTypeSecurityPolicy, + RequestType: guestrequest.RequestTypeAdd, + Settings: *confOpts, }, }