From 734a0ed8dff0112ce3a12a0f4b892e1a5095eca6 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Mon, 5 Dec 2022 17:05:25 -0800 Subject: [PATCH] plumb AMD certs to workload containers (#1549) confidential containers: Add AMD cert plumbing Add logic to plumb AMD certificates to workload containers. The assumption is that the certificates will be "fresh enough" for necessary attestation and key release by the workflow and third party services. Additionally add error logging when UVM reference info file is not found Signed-off-by: Maksim An --- internal/guest/runtime/hcsv2/uvm.go | 6 ++++-- internal/oci/annotations.go | 2 +- internal/uvm/security_policy.go | 25 +++++++++++++++++++++---- internal/uvm/start.go | 5 +++-- pkg/annotations/annotations.go | 8 ++++++-- test/cri-containerd/policy_test.go | 24 +++++++++++++++--------- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index 25c400706a..778bde5740 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -343,8 +343,10 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM // construction time. if oci.ParseAnnotationsBool(ctx, settings.OCISpecification.Annotations, annotations.UVMSecurityPolicyEnv, false) { secPolicyEnv := fmt.Sprintf("UVM_SECURITY_POLICY=%s", h.securityPolicyEnforcer.EncodedSecurityPolicy()) - uvmReferenceInfo := fmt.Sprintf("UVM_REFERENCE_INFO=%s", h.uvmReferenceInfo) - settings.OCISpecification.Process.Env = append(settings.OCISpecification.Process.Env, secPolicyEnv, uvmReferenceInfo) + uvmReferenceInfoEnv := fmt.Sprintf("UVM_REFERENCE_INFO=%s", h.uvmReferenceInfo) + amdCertEnv := fmt.Sprintf("UVM_HOST_AMD_CERTIFICATE=%s", settings.OCISpecification.Annotations[annotations.HostAMDCertificate]) + settings.OCISpecification.Process.Env = append(settings.OCISpecification.Process.Env, + secPolicyEnv, uvmReferenceInfoEnv, amdCertEnv) } // Create the BundlePath diff --git a/internal/oci/annotations.go b/internal/oci/annotations.go index 2fdd44a259..66396160a2 100644 --- a/internal/oci/annotations.go +++ b/internal/oci/annotations.go @@ -20,7 +20,7 @@ func ProcessAnnotations(ctx context.Context, s *specs.Spec) (err error) { // Named `Process` and not `Expand` since this function may be expanded (pun intended) to // deal with other annotation issues and validation. - // Rathen than give up part of the way through on error, this just emits a warning (similar + // Rather than give up part of the way through on error, this just emits a warning (similar // to the `parseAnnotation*` functions) and continues through, so the spec is not left in a // (partially) unusable form. // If multiple different errors are to be raised, they should be combined or, if they diff --git a/internal/uvm/security_policy.go b/internal/uvm/security_policy.go index 5a96d7dc6b..b16714e1fb 100644 --- a/internal/uvm/security_policy.go +++ b/internal/uvm/security_policy.go @@ -10,6 +10,7 @@ import ( "path/filepath" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/log" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" "github.com/Microsoft/hcsshim/internal/protocol/guestresource" "github.com/Microsoft/hcsshim/pkg/ctrdtaskapi" @@ -42,6 +43,17 @@ func WithSecurityPolicyEnforcer(enforcer string) ConfidentialUVMOpt { } } +func base64EncodeFileContents(filePath string) (string, error) { + if filePath == "" { + return "", nil + } + content, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(content), 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. @@ -50,11 +62,16 @@ func WithUVMReferenceInfo(referenceRoot string, referenceName string) Confidenti if referenceName == "" { return nil } - content, err := os.ReadFile(filepath.Join(referenceRoot, referenceName)) - if err != nil && !os.IsNotExist(err) { - return err + fullFilePath := filepath.Join(referenceRoot, referenceName) + encoded, err := base64EncodeFileContents(fullFilePath) + if err != nil { + if os.IsNotExist(err) { + log.G(ctx).WithField("filePath", fullFilePath).Debug("UVM reference info file not found") + return nil + } + return fmt.Errorf("failed to read UVM reference info file: %w", err) } - r.EncodedUVMReference = base64.StdEncoding.EncodeToString(content) + r.EncodedUVMReference = encoded return nil } } diff --git a/internal/uvm/start.go b/internal/uvm/start.go index 69daaa6e69..adbb87271c 100644 --- a/internal/uvm/start.go +++ b/internal/uvm/start.go @@ -282,11 +282,12 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) { } if uvm.confidentialUVMOptions != nil && uvm.OS() == "linux" { - if err := uvm.SetConfidentialUVMOptions(ctx, + copts := []ConfidentialUVMOpt{ WithSecurityPolicy(uvm.confidentialUVMOptions.SecurityPolicy), WithSecurityPolicyEnforcer(uvm.confidentialUVMOptions.SecurityPolicyEnforcer), WithUVMReferenceInfo(defaultLCOWOSBootFilesPath(), uvm.confidentialUVMOptions.UVMReferenceInfoFile), - ); err != nil { + } + if err := uvm.SetConfidentialUVMOptions(ctx, copts...); err != nil { return err } } diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index e2c1e7a642..74ba7cc379 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -271,13 +271,17 @@ const ( // GuestStateFile specifies the path of the vmgs file to use if required. Only applies in SNP mode. GuestStateFile = "io.microsoft.virtualmachine.lcow.gueststatefile" - // UVMSecurityPolicyEnv specifies if UVM_SECURITY_POLICY and - // UVM_REFERENCE_INFO variables should be injected for a container. + // UVMSecurityPolicyEnv specifies if UVM_SECURITY_POLICY, UVM_REFERENCE_INFO + // and UVM_HOST_AMD_CERTIFICATE variables should be injected for a container. UVMSecurityPolicyEnv = "io.microsoft.virtualmachine.lcow.securitypolicy.env" // UVMReferenceInfoFile specifies the filename of a signed UVM reference file to be passed to UVM. UVMReferenceInfoFile = "io.microsoft.virtualmachine.lcow.uvm-reference-info-file" + // HostAMDCertificate specifies the filename of the AMD certificates to be passed to UVM. + // The certificate is expected to be located in the same directory as the shim executable. + HostAMDCertificate = "io.microsoft.virtualmachine.lcow.amd-certificate" + // DisableLCOWTimeSyncService is used to disable the chronyd time // synchronization service inside the LCOW UVM. DisableLCOWTimeSyncService = "io.microsoft.virtualmachine.lcow.timesync.disable" diff --git a/test/cri-containerd/policy_test.go b/test/cri-containerd/policy_test.go index 28f0b179c5..151b11d2d5 100644 --- a/test/cri-containerd/policy_test.go +++ b/test/cri-containerd/policy_test.go @@ -790,9 +790,11 @@ func Test_RunContainer_WithPolicy_And_SecurityPolicyEnv_Annotation(t *testing.T) alpineCmd, sandboxRequest.Config, ) + certValue := "dummy-cert-value" if setPolicyEnv { containerRequest.Config.Annotations = map[string]string{ annotations.UVMSecurityPolicyEnv: "true", + annotations.HostAMDCertificate: certValue, } } // setup logfile to capture stdout @@ -812,19 +814,23 @@ func Test_RunContainer_WithPolicy_And_SecurityPolicyEnv_Annotation(t *testing.T) if err != nil { t.Fatalf("error reading log file: %s", err) } - policyEnv := fmt.Sprintf("UVM_SECURITY_POLICY=%s", config.policy) - measurementEnv := "UVM_REFERENCE_INFO=" + targetEnvs := []string{ + fmt.Sprintf("UVM_SECURITY_POLICY=%s", config.policy), + "UVM_REFERENCE_INFO=", + fmt.Sprintf("UVM_HOST_AMD_CERTIFICATE=%s", certValue), + } if setPolicyEnv { // make sure that the expected environment variable was set - if !strings.Contains(string(content), policyEnv) || !strings.Contains(string(content), measurementEnv) { - t.Fatalf("UVM_SECURITY_POLICY and UVM_REFERENCE_INFO env vars should be set for init"+ - " process:\n%s\n", string(content)) + for _, env := range targetEnvs { + if !strings.Contains(string(content), env) { + t.Fatalf("missing init process environment variable: %s", env) + } } } else { - if strings.Contains(string(content), policyEnv) || strings.Contains(string(content), measurementEnv) { - t.Fatalf("UVM_SECURITY_POLICY and UVM_REFERENCE_INFO env vars shouldn't be set for init"+ - " process:\n%s\n", - string(content)) + for _, env := range targetEnvs { + if strings.Contains(string(content), env) { + t.Fatalf("environment variable should not be set for init process: %s", env) + } } } })