From f95b6f8fd57ce75d898957909395b94b5ef905ab Mon Sep 17 00:00:00 2001 From: Emanuel Oprea <2664342+EmanuelOprea@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:34:36 +0300 Subject: [PATCH 1/2] securitypolicy: honor LCOW AMD certificate annotation Read the LCOW AMD certificate annotation when writing the shared security context, while retaining the WCOW annotation as a fallback for existing callers. The deprecated generic annotation already aliases the LCOW key. Add cross-platform regression coverage for certificate selection, empty inputs, and preservation of the other context files and environment. Validation: reproduced the regression before the fix on Windows and Linux; the targeted tests and Rego securitypolicy suites pass on both platforms, and the Windows GCS sidecar and Linux GCS build successfully with Go 1.26.8. Signed-off-by: Emanuel Oprea <2664342+EmanuelOprea@users.noreply.github.com> --- pkg/securitypolicy/securitypolicy_options.go | 7 +- .../securitypolicy_options_test.go | 140 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 pkg/securitypolicy/securitypolicy_options_test.go diff --git a/pkg/securitypolicy/securitypolicy_options.go b/pkg/securitypolicy/securitypolicy_options.go index 12f28428b8..c0d87a0fee 100644 --- a/pkg/securitypolicy/securitypolicy_options.go +++ b/pkg/securitypolicy/securitypolicy_options.go @@ -461,7 +461,12 @@ func (s *SecurityOptions) WriteSecurityContextDir(spec *specs.Spec) (string, err platformReferenceInfoBase64 = base64.StdEncoding.EncodeToString(s.platformReferenceInfo) } s.platformReferenceInfoMutex.RUnlock() - hostAMDCert := spec.Annotations[annotations.WCOWHostAMDCertificate] + // HostAMDCertificate is a deprecated alias of the LCOW key. Prefer that + // key, but preserve support for callers that supply the WCOW key. + hostAMDCert := spec.Annotations[annotations.LCOWHostAMDCertificate] + if hostAMDCert == "" { + hostAMDCert = spec.Annotations[annotations.WCOWHostAMDCertificate] + } if len(encodedPolicy) > 0 || len(hostAMDCert) > 0 || len(s.UvmReferenceInfo) > 0 || len(s.UvmHashEnvelopeReferenceInfo) > 0 || len(tcbReferenceInfoBase64) > 0 || len(platformReferenceInfoBase64) > 0 { // Use os.MkdirTemp to make sure that the directory is unique. securityContextDir, err := os.MkdirTemp(spec.Root.Path, SecurityContextDirTemplate) diff --git a/pkg/securitypolicy/securitypolicy_options_test.go b/pkg/securitypolicy/securitypolicy_options_test.go new file mode 100644 index 0000000000..ef1e22ca1e --- /dev/null +++ b/pkg/securitypolicy/securitypolicy_options_test.go @@ -0,0 +1,140 @@ +package securitypolicy + +import ( + "encoding/base64" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/Microsoft/hcsshim/pkg/annotations" + "github.com/opencontainers/runtime-spec/specs-go" +) + +func TestWriteSecurityContextDirAMDCertificate(t *testing.T) { + const lcowCert = "bGNvdy1jZXJ0" + const wcowCert = "d2Nvdy1jZXJ0" + for _, tc := range []struct { + name string + annotations map[string]string + wantCert string + }{ + { + name: "LCOW", + annotations: map[string]string{annotations.LCOWHostAMDCertificate: lcowCert}, + wantCert: lcowCert, + }, + { + name: "deprecated generic alias", + annotations: map[string]string{annotations.HostAMDCertificate: lcowCert}, + wantCert: lcowCert, + }, + { + name: "WCOW fallback", + annotations: map[string]string{annotations.WCOWHostAMDCertificate: wcowCert}, + wantCert: wcowCert, + }, + { + name: "LCOW takes precedence", + annotations: map[string]string{ + annotations.LCOWHostAMDCertificate: lcowCert, + annotations.WCOWHostAMDCertificate: wcowCert, + }, + wantCert: lcowCert, + }, + { + name: "empty LCOW falls back to WCOW", + annotations: map[string]string{ + annotations.LCOWHostAMDCertificate: "", + annotations.WCOWHostAMDCertificate: wcowCert, + }, + wantCert: wcowCert, + }, + {name: "nil annotations"}, + {name: "empty annotations", annotations: map[string]string{}}, + { + name: "empty certificates", + annotations: map[string]string{ + annotations.LCOWHostAMDCertificate: "", + annotations.WCOWHostAMDCertificate: "", + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + for _, withOtherContext := range []bool{false, true} { + name := "certificate only" + if withOtherContext { + name = "with other security context" + } + t.Run(name, func(t *testing.T) { + root := t.TempDir() + spec := &specs.Spec{ + Root: &specs.Root{Path: root}, + Process: &specs.Process{Env: []string{"EXISTING=value"}}, + Annotations: tc.annotations, + } + enforcer := &OpenDoorSecurityPolicyEnforcer{} + options := &SecurityOptions{PolicyEnforcer: enforcer} + wantFiles := map[string]string{} + if tc.wantCert != "" { + wantFiles[HostAMDCertFilename] = tc.wantCert + } + if withOtherContext { + enforcer.encodedSecurityPolicy = "cG9saWN5" + options.UvmReferenceInfo = "cmVmZXJlbmNl" + options.UvmHashEnvelopeReferenceInfo = "aGFzaC1lbnZlbG9wZQ==" + options.tcbReferenceInfo = []byte("tcb") + options.platformReferenceInfo = []byte("platform") + wantFiles[PolicyFilename] = enforcer.encodedSecurityPolicy + wantFiles[ReferenceInfoFilename] = options.UvmReferenceInfo + wantFiles[HashEnvelopeReferenceInfoFilename] = options.UvmHashEnvelopeReferenceInfo + wantFiles[TCBReferenceInfoFilename] = base64.StdEncoding.EncodeToString(options.tcbReferenceInfo) + wantFiles[PlatformReferenceInfoFilename] = base64.StdEncoding.EncodeToString(options.platformReferenceInfo) + } + + dir, err := options.WriteSecurityContextDir(spec) + if err != nil { + t.Fatal(err) + } + wantEnv := []string{"EXISTING=value"} + if len(wantFiles) == 0 { + if dir != "" { + t.Fatalf("unexpected security context directory %q", dir) + } + entries, err := os.ReadDir(root) + if err != nil { + t.Fatal(err) + } + if len(entries) != 0 { + t.Fatalf("unexpected rootfs entries: %v", entries) + } + } else { + if dir == "" || filepath.Dir(dir) != root { + t.Fatalf("expected security context directory under %q, got %q", root, dir) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + if len(entries) != len(wantFiles) { + t.Fatalf("expected %d context files, got %d", len(wantFiles), len(entries)) + } + for filename, want := range wantFiles { + got, err := os.ReadFile(filepath.Join(dir, filename)) + if err != nil { + t.Fatal(err) + } + if string(got) != want { + t.Errorf("%s: got %q, want %q", filename, got, want) + } + } + wantEnv = append(wantEnv, "UVM_SECURITY_CONTEXT_DIR=/"+filepath.Base(dir)) + } + if !reflect.DeepEqual(spec.Process.Env, wantEnv) { + t.Errorf("environment: got %v, want %v", spec.Process.Env, wantEnv) + } + }) + } + }) + } +} From 21489bd283d12cf4c59a5cb982d0e867a6428b74 Mon Sep 17 00:00:00 2001 From: Emanuel Oprea <2664342+EmanuelOprea@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:59:51 +0300 Subject: [PATCH 2/2] test: allow intentional deprecated AMD certificate alias coverage Keep the deprecated alias compatibility case and scope the staticcheck SA1019 exception to its intentional use. This follows existing repository lint conventions without weakening checks for the rest of the test or changing runtime behavior. Validation: the targeted certificate regression test passes, and golangci-lint 2.11.4 reports zero issues for pkg/securitypolicy on Windows default, LCOW and WCOW tags and Linux default tags. Signed-off-by: Emanuel Oprea <2664342+EmanuelOprea@users.noreply.github.com> --- pkg/securitypolicy/securitypolicy_options_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/securitypolicy/securitypolicy_options_test.go b/pkg/securitypolicy/securitypolicy_options_test.go index ef1e22ca1e..c17896dad2 100644 --- a/pkg/securitypolicy/securitypolicy_options_test.go +++ b/pkg/securitypolicy/securitypolicy_options_test.go @@ -26,7 +26,7 @@ func TestWriteSecurityContextDirAMDCertificate(t *testing.T) { }, { name: "deprecated generic alias", - annotations: map[string]string{annotations.HostAMDCertificate: lcowCert}, + annotations: map[string]string{annotations.HostAMDCertificate: lcowCert}, //nolint:staticcheck // SA1019: intentionally test compatibility with the deprecated alias. wantCert: lcowCert, }, {