diff --git a/cli/cmd/common.go b/cli/cmd/common.go index e3cba7a3dc1..257907761cd 100644 --- a/cli/cmd/common.go +++ b/cli/cmd/common.go @@ -13,6 +13,7 @@ import ( "log/slog" "os" "path/filepath" + "strconv" "strings" "time" @@ -38,8 +39,18 @@ const ( latestTransitionHashFilename = "latest-transition" historyFilename = "history.yml" verifyDir = "verify" + // allowInsecureEnvVar gates insecure (non-CC) runtime support and matches the + // Coordinator's env var of the same name. + allowInsecureEnvVar = "CONTRAST_ALLOW_INSECURE" ) +// insecureRuntimesAllowed reports whether allowInsecureEnvVar is set to a value that parses as +// true. Commands only declare the --INSECURE flag when this returns true. +func insecureRuntimesAllowed() bool { + allowed, err := strconv.ParseBool(os.Getenv(allowInsecureEnvVar)) + return err == nil && allowed +} + // ReleaseImageReplacements contains the image replacements used by contrast. // //go:embed assets/image-replacements.txt diff --git a/cli/cmd/generate.go b/cli/cmd/generate.go index f9bf4a1c3fc..00c5e7da821 100644 --- a/cli/cmd/generate.go +++ b/cli/cmd/generate.go @@ -93,6 +93,9 @@ subcommands.`, cmd.Flags().Bool("insecure-enable-debug-shell-access", false, "enable the debug shell service in the pod CVM to get access from container to guest VM") cmd.Flags().Bool("calculate-pod-memory", false, "calculate pod memory based on image layer sizes and container resource limits") cmd.Flags().StringP("output", "o", "", "output file for generated YAML") + if insecureRuntimesAllowed() { + cmd.Flags().Bool("INSECURE", false, "allow generation for insecure (non-CC) runtimes") + } must(cmd.MarkFlagFilename("policy", "rego")) must(cmd.MarkFlagFilename("settings", "json")) must(cmd.MarkFlagFilename("manifest", "json")) @@ -144,6 +147,10 @@ func runGenerate(cmd *cobra.Command, args []string) error { usedPlatforms.Add(flags.referenceValuesPlatform) } + if err := validateInsecurePlatforms(usedPlatforms, flags.allowInsecureRuntimes); err != nil { + return err + } + // generate a manifest by checking if a manifest exists and using that, // or otherwise using a default. var mnf *manifest.Manifest @@ -294,7 +301,7 @@ func runGenerate(cmd *cobra.Command, args []string) error { return nil } -// mapContrastWorkloads applies the given function to all workloads with a Contrast runtime class. +// mapContrastWorkloads applies the given function to all workloads with the 'contrast-cc' or 'contrast-insecure' runtime class. // The callback receives an apply configuration together with the file path and index the unstructured object has in the file map. // Changes to the apply configuration are not applied to the original unstructured object. func mapContrastWorkloads(fileMap map[string][]*unstructured.Unstructured, f func(res any, path string, idx int) (any, error)) error { @@ -343,6 +350,16 @@ func isCoordinator(resource any) bool { return false } +func patchCoordinatorAllowInsecure(resource any) { + r, ok := resource.(*applyappsv1.StatefulSetApplyConfiguration) + if !ok || !isCoordinator(resource) { + return + } + if len(r.Spec.Template.Spec.Containers) > 0 { + r.Spec.Template.Spec.Containers[0].WithEnv(kuberesource.NewEnvVar(allowInsecureEnvVar, "1")) + } +} + func runVerifiers(fileMap map[string][]*unstructured.Unstructured, verifiers []verifier.Verifier) error { var findings error for _, v := range verifiers { @@ -428,7 +445,7 @@ func extractTargets(paths []string, configFile io.Writer, logger *slog.Logger) ( } } if len(fileMap) == 0 { - return nil, "", fmt.Errorf("no .yml/.yaml files with 'contrast-cc' runtime found") + return nil, "", fmt.Errorf("no .yml/.yaml files with 'contrast-cc' or 'contrast-insecure' runtime found") } extraData, err := kuberesource.EncodeUnstructured(extraResources) @@ -586,6 +603,9 @@ func patchTargets(fileMap map[string][]*unstructured.Unstructured, imageReplacem if flags.injectImageStore { kuberesource.AddImageStore([]any{res}) } + if flags.allowInsecureRuntimes { + patchCoordinatorAllowInsecure(res) + } kuberesource.PatchImages([]any{res}, replacements) @@ -631,6 +651,16 @@ func injectServiceMesh(resource any, memoryProfile kuberesource.MemoryProfile) e return nil } +func validateInsecurePlatforms(usedPlatforms kuberesource.PlatformCollection, allowInsecure bool) error { + if !slices.ContainsFunc(usedPlatforms.Platforms(), platforms.IsInsecure) { + return nil + } + if !allowInsecure { + return fmt.Errorf("insecure runtime platforms detected but --INSECURE flag not set (the flag is only available with the %s environment variable set to true)", allowInsecureEnvVar) + } + return nil +} + func validateOutputFile(outputFile string) error { if outputFile == "" { return nil @@ -758,7 +788,17 @@ func patchRuntimeClassName(defaultRuntimeHandler string) func(*applycorev1.PodSp if spec == nil || spec.RuntimeClassName == nil { return spec, nil } - if *spec.RuntimeClassName == "kata-cc-isolation" || *spec.RuntimeClassName == "contrast-cc" { + if *spec.RuntimeClassName == "kata-cc-isolation" || *spec.RuntimeClassName == "contrast-cc" || *spec.RuntimeClassName == "contrast-insecure" { + // Only allow the bare runtime class names if the default runtime handler is compatible. + // For example, `contrast-cc` should only resolve when `--reference-values` is set to a CC-enabled platform, + // and `contrast-insecure` should only resolve when `--reference-values` is set to an insecure platform. + if *spec.RuntimeClassName == "contrast-insecure" && !strings.HasPrefix(defaultRuntimeHandler, "contrast-insecure-") { + return nil, fmt.Errorf("bare 'contrast-insecure' runtime class requires --reference-values to be set to an insecure platform") + } + if (*spec.RuntimeClassName == "contrast-cc" || *spec.RuntimeClassName == "kata-cc-isolation") && + strings.HasPrefix(defaultRuntimeHandler, "contrast-insecure-") { + return nil, fmt.Errorf("bare %q runtime class is incompatible with insecure --reference-values platform %q", *spec.RuntimeClassName, defaultRuntimeHandler) + } spec.RuntimeClassName = &defaultRuntimeHandler if kuberesource.PodSpecRequiresGPU(spec) { platform, err := platforms.FromRuntimeClassString(*spec.RuntimeClassName) @@ -773,7 +813,7 @@ func patchRuntimeClassName(defaultRuntimeHandler string) func(*applycorev1.PodSp } return spec, nil } - if !strings.HasPrefix(*spec.RuntimeClassName, "contrast-cc-") { + if !kuberesource.IsContrastPod(spec) { return spec, nil } overridePlatform, err := platforms.FromRuntimeClassString(*spec.RuntimeClassName) @@ -961,6 +1001,7 @@ type generateFlags struct { injectImageStore bool insecureEnableDebugShell bool calculatePodMemory bool + allowInsecureRuntimes bool outputFile string } @@ -1066,6 +1107,13 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { if err != nil { return nil, err } + allowInsecureRuntimes := false + if cmd.Flags().Lookup("INSECURE") != nil { + allowInsecureRuntimes, err = cmd.Flags().GetBool("INSECURE") + if err != nil { + return nil, err + } + } outputFile, err := cmd.Flags().GetString("output") if err != nil { return nil, err @@ -1093,6 +1141,7 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { injectImageStore: injectImageStore, insecureEnableDebugShell: insecureEnableDebugShell, calculatePodMemory: calculatePodMemory, + allowInsecureRuntimes: allowInsecureRuntimes, outputFile: outputFile, }, nil } diff --git a/cli/cmd/generate_test.go b/cli/cmd/generate_test.go index d6c76415ca3..b39fb4769c4 100644 --- a/cli/cmd/generate_test.go +++ b/cli/cmd/generate_test.go @@ -4,6 +4,7 @@ package cmd import ( + "os" "testing" "github.com/edgelesssys/contrast/cli/genpolicy" @@ -79,6 +80,40 @@ spec: }, want: []platforms.Platform{platforms.MetalQEMUSNP, platforms.MetalQEMUTDX}, }, + "single insecure": { + yaml: map[string]string{ + "file1.yaml": ` +apiVersion: v1 +kind: Pod +metadata: + name: p1 +spec: + runtimeClassName: contrast-insecure-metal-qemu +`, + }, + want: []platforms.Platform{platforms.MetalQEMUInsecure}, + }, + "mixed cc and insecure": { + yaml: map[string]string{ + "file1.yaml": ` +apiVersion: v1 +kind: Pod +metadata: + name: p1 +spec: + runtimeClassName: contrast-cc-metal-qemu-snp +`, + "file2.yaml": ` +apiVersion: v1 +kind: Pod +metadata: + name: p2 +spec: + runtimeClassName: contrast-insecure-metal-qemu +`, + }, + want: []platforms.Platform{platforms.MetalQEMUSNP, platforms.MetalQEMUInsecure}, + }, } for name, tc := range testCases { @@ -101,33 +136,67 @@ spec: } func TestPatchRuntimeClassName(t *testing.T) { - defaultHandler := "contrast-cc-metal-qemu-snp" + ccHandler := "contrast-cc-metal-qemu-snp" + insecureHandler := "contrast-insecure-metal-qemu" testCases := map[string]struct { - initial string - want string - updateHandler bool + defaultHandler string + initial string + want string + updateHandler bool + wantErr bool }{ "no runtime class": { - initial: "", - want: "", + defaultHandler: ccHandler, + initial: "", + want: "", }, "irrelevant class": { - initial: "runc", - want: "runc", + defaultHandler: ccHandler, + initial: "runc", + want: "runc", }, "generic kata": { - initial: "kata-cc-isolation", - want: defaultHandler, + defaultHandler: ccHandler, + initial: "kata-cc-isolation", + want: ccHandler, }, "generic contrast": { - initial: "contrast-cc", - want: defaultHandler, + defaultHandler: ccHandler, + initial: "contrast-cc", + want: ccHandler, }, "specific contrast-cc-metal-qemu-tdx": { - initial: "contrast-cc-metal-qemu-tdx", - want: "contrast-cc-metal-qemu-tdx", - updateHandler: true, + defaultHandler: ccHandler, + initial: "contrast-cc-metal-qemu-tdx", + want: "contrast-cc-metal-qemu-tdx", + updateHandler: true, + }, + "generic contrast-insecure with insecure handler": { + defaultHandler: insecureHandler, + initial: "contrast-insecure", + want: insecureHandler, + }, + "generic contrast-insecure with cc handler errors": { + defaultHandler: ccHandler, + initial: "contrast-insecure", + wantErr: true, + }, + "generic contrast-cc with insecure handler errors": { + defaultHandler: insecureHandler, + initial: "contrast-cc", + wantErr: true, + }, + "generic kata with insecure handler errors": { + defaultHandler: insecureHandler, + initial: "kata-cc-isolation", + wantErr: true, + }, + "specific contrast-insecure-metal-qemu": { + defaultHandler: ccHandler, + initial: "contrast-insecure-metal-qemu", + want: "contrast-insecure-metal-qemu", + updateHandler: true, }, } @@ -143,12 +212,16 @@ func TestPatchRuntimeClassName(t *testing.T) { tc.want = getHandler(t, tc.want) } - patch := patchRuntimeClassName(tc.want) + patch := patchRuntimeClassName(tc.defaultHandler) spec := applycorev1.PodSpec() if tc.initial != "" { spec.WithRuntimeClassName(tc.initial) } _, err := patch(spec) + if tc.wantErr { + require.Error(t, err) + return + } require.NoError(t, err) if tc.want == "" { assert.Nil(t, spec.RuntimeClassName) @@ -160,13 +233,129 @@ func TestPatchRuntimeClassName(t *testing.T) { } t.Run("nil spec returns nil", func(t *testing.T) { - patch := patchRuntimeClassName(defaultHandler) + patch := patchRuntimeClassName(ccHandler) result, err := patch(nil) require.NoError(t, err) assert.Nil(t, result) }) } +func TestIsContrastWorkload(t *testing.T) { + testCases := map[string]struct { + runtimeClass string + want bool + }{ + "no runtime class": { + runtimeClass: "", + want: false, + }, + "non-contrast runtime class": { + runtimeClass: "foobar", + want: false, + }, + "contrast-cc": { + runtimeClass: "contrast-cc", + want: true, + }, + "contrast-cc-metal-qemu-snp": { + runtimeClass: "contrast-cc-metal-qemu-snp", + want: true, + }, + "contrast-insecure": { + runtimeClass: "contrast-insecure", + want: true, + }, + "contrast-insecure-metal-qemu": { + runtimeClass: "contrast-insecure-metal-qemu", + want: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + spec := applycorev1.PodSpec() + if tc.runtimeClass != "" { + spec.WithRuntimeClassName(tc.runtimeClass) + } + pod := applycorev1.Pod("test", "default").WithSpec(spec) + assert.Equal(t, tc.want, isContrastWorkload(pod)) + }) + } +} + +func TestValidateInsecurePlatforms(t *testing.T) { + testCases := map[string]struct { + platforms []platforms.Platform + allowInsecure bool + wantErr bool + wantErrContain string + }{ + "no insecure platforms": { + platforms: []platforms.Platform{platforms.MetalQEMUSNP}, + wantErr: false, + }, + "insecure without flag": { + platforms: []platforms.Platform{platforms.MetalQEMUInsecure}, + allowInsecure: false, + wantErr: true, + wantErrContain: "--INSECURE flag not set", + }, + "insecure with flag": { + platforms: []platforms.Platform{platforms.MetalQEMUInsecure}, + allowInsecure: true, + wantErr: false, + }, + "mixed with flag": { + platforms: []platforms.Platform{platforms.MetalQEMUSNP, platforms.MetalQEMUInsecure}, + allowInsecure: true, + wantErr: false, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + collection := kuberesource.PlatformCollection{} + for _, p := range tc.platforms { + collection.Add(p) + } + + err := validateInsecurePlatforms(collection, tc.allowInsecure) + if tc.wantErr { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErrContain) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestInsecureRuntimesAllowed(t *testing.T) { + testCases := map[string]struct { + set bool + value string + want bool + }{ + "unset": {set: false, want: false}, + "empty": {set: true, value: "", want: false}, + "true": {set: true, value: "true", want: true}, + "one": {set: true, value: "1", want: true}, + "false": {set: true, value: "false", want: false}, + "garbage": {set: true, value: "yes please", want: false}, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + t.Setenv("CONTRAST_ALLOW_INSECURE", tc.value) + if !tc.set { + os.Unsetenv("CONTRAST_ALLOW_INSECURE") + } + + assert.Equal(t, tc.want, insecureRuntimesAllowed()) + }) + } +} + func getHandler(t *testing.T, name string) string { t.Helper() platform, err := platforms.FromRuntimeClassString(name) diff --git a/cli/cmd/verify.go b/cli/cmd/verify.go index 89a77ef205b..56a1e93f933 100644 --- a/cli/cmd/verify.go +++ b/cli/cmd/verify.go @@ -49,6 +49,9 @@ all policies, and the certificates of the Coordinator certificate authority.`, cmd.Flags().StringP("coordinator", "c", "", "endpoint the coordinator can be reached at") must(cobra.MarkFlagRequired(cmd.Flags(), "coordinator")) addCollateralProxyFlag(cmd) + if insecureRuntimesAllowed() { + cmd.Flags().Bool("INSECURE", false, "allow verification of insecure (non-CC) deployments") + } return cmd } @@ -70,13 +73,21 @@ func runVerify(cmd *cobra.Command, _ []string) error { return fmt.Errorf("failed to read manifest file: %w", err) } + var mnfst manifest.Manifest + if err := json.Unmarshal(manifestBytes, &mnfst); err != nil { + return fmt.Errorf("unmarshalling manifest: %w", err) + } + if mnfst.HasInsecurePlatforms() && !flags.allowInsecureRuntimes { + return fmt.Errorf("manifest contains insecure platforms but --INSECURE flag not set (the flag is only available with the %s environment variable set to true)", allowInsecureEnvVar) + } + kdsDir, err := cachedir("kds") if err != nil { return fmt.Errorf("getting cache dir: %w", err) } log.Debug("Using KDS cache dir", "dir", kdsDir) - resp, err := getCoordinatorState(cmd.Context(), kdsDir, manifestBytes, flags.coordinator, flags.collateralProxyURL, log) + resp, err := getCoordinatorState(cmd.Context(), kdsDir, mnfst, flags.coordinator, flags.collateralProxyURL, log) if err != nil { return fmt.Errorf("getting manifests: %w", err) } @@ -131,10 +142,11 @@ func runVerify(cmd *cobra.Command, _ []string) error { } type verifyFlags struct { - manifestPath string - coordinator string - workspaceDir string - collateralProxyURL string + manifestPath string + coordinator string + workspaceDir string + collateralProxyURL string + allowInsecureRuntimes bool } func parseVerifyFlags(cmd *cobra.Command) (*verifyFlags, error) { @@ -154,6 +166,13 @@ func parseVerifyFlags(cmd *cobra.Command) (*verifyFlags, error) { if err != nil { return nil, err } + allowInsecureRuntimes := false + if cmd.Flags().Lookup("INSECURE") != nil { + allowInsecureRuntimes, err = cmd.Flags().GetBool("INSECURE") + if err != nil { + return nil, err + } + } if workspaceDir != "" { // Prepend default path with workspaceDir @@ -163,10 +182,11 @@ func parseVerifyFlags(cmd *cobra.Command) (*verifyFlags, error) { } return &verifyFlags{ - manifestPath: manifestPath, - coordinator: coordinator, - workspaceDir: workspaceDir, - collateralProxyURL: collateralProxyURL, + manifestPath: manifestPath, + coordinator: coordinator, + workspaceDir: workspaceDir, + collateralProxyURL: collateralProxyURL, + allowInsecureRuntimes: allowInsecureRuntimes, }, nil } @@ -186,11 +206,7 @@ func writeFilelist(dir string, filelist map[string][]byte) error { } // getCoordinatorState calls GetManifests on the coordinator's userapi via aTLS. -func getCoordinatorState(ctx context.Context, kdsDir string, manifestBytes []byte, endpoint, collateralProxy string, log *slog.Logger) (sdk.CoordinatorState, error) { - var m manifest.Manifest - if err := json.Unmarshal(manifestBytes, &m); err != nil { - return sdk.CoordinatorState{}, fmt.Errorf("unmarshalling manifest: %w", err) - } +func getCoordinatorState(ctx context.Context, kdsDir string, m manifest.Manifest, endpoint, collateralProxy string, log *slog.Logger) (sdk.CoordinatorState, error) { if err := m.Validate(); err != nil { return sdk.CoordinatorState{}, fmt.Errorf("validating manifest: %w", err) } diff --git a/cli/genpolicy/genpolicy.go b/cli/genpolicy/genpolicy.go index b61be02404e..f689e69e08d 100644 --- a/cli/genpolicy/genpolicy.go +++ b/cli/genpolicy/genpolicy.go @@ -56,6 +56,7 @@ func New(rulesPath, settingsPath, cachePath string, bin []byte) (*Runner, error) func (r *Runner) Run(ctx context.Context, res any, extraPath string, needLayersCache bool, logger *slog.Logger) (string, *LayersCache, error) { args := []string{ "--runtime-class-names=contrast-cc", + "--runtime-class-names=contrast-insecure", "--rego-rules-path=" + r.rulesPath, "--json-settings-path=" + r.settingsPath, "--layers-cache-file-path=" + r.cachePath, diff --git a/cli/verifier/image_ref_valid.go b/cli/verifier/image_ref_valid.go index cb25b388ab7..576746961e6 100644 --- a/cli/verifier/image_ref_valid.go +++ b/cli/verifier/image_ref_valid.go @@ -26,6 +26,7 @@ func (v *ImageRefValid) Verify(toVerify any) error { spec *applycorev1.PodSpecApplyConfiguration, ) *applycorev1.PodSpecApplyConfiguration { if !kuberesource.IsContrastPod(spec) { + // Non-Contrast pods are not subject to this verification. return spec } diff --git a/cli/verifier/no_shared_fs_mount.go b/cli/verifier/no_shared_fs_mount.go index befc92bedd8..478c3367566 100644 --- a/cli/verifier/no_shared_fs_mount.go +++ b/cli/verifier/no_shared_fs_mount.go @@ -25,7 +25,7 @@ func (v *NoSharedFSMount) Verify(toVerify any) error { isNonCC := false kuberesource.MapPodSpec(toVerify, func(spec *applycorev1.PodSpecApplyConfiguration) *applycorev1.PodSpecApplyConfiguration { if !kuberesource.IsContrastPod(spec) { - // this isn't a confidential pod so we don't need to check further + // this isn't a Contrast pod so we don't need to check further isNonCC = true return spec } diff --git a/cli/verifier/runtimeclasses_exist.go b/cli/verifier/runtimeclasses_exist.go index c6dceee669b..f2a25732f55 100644 --- a/cli/verifier/runtimeclasses_exist.go +++ b/cli/verifier/runtimeclasses_exist.go @@ -6,7 +6,6 @@ package verifier import ( "errors" "fmt" - "strings" "github.com/edgelesssys/contrast/internal/kuberesource" "github.com/edgelesssys/contrast/internal/platforms" @@ -15,12 +14,12 @@ import ( applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" ) -// RuntimeClassesExist verifies that all used contrast-cc -prefixed runtimeClassNames are valid. +// RuntimeClassesExist verifies that all used contrast-cc or contrast-insecure prefixed runtimeClassNames are valid. type RuntimeClassesExist struct { Command *cobra.Command } -// Verify verifies that all used contrast-cc -prefixed runtimeClassNames are valid. +// Verify verifies that all used contrast-cc or contrast-insecure prefixed runtimeClassNames are valid. func (r *RuntimeClassesExist) Verify(toVerify any) error { var collectedErrs error collectedMissingRuntimes := map[string]error{} @@ -31,14 +30,15 @@ func (r *RuntimeClassesExist) Verify(toVerify any) error { } kuberesource.MapPodSpec(toVerify, func(spec *applycorev1.PodSpecApplyConfiguration) *applycorev1.PodSpecApplyConfiguration { - if spec == nil || spec.RuntimeClassName == nil { + if !kuberesource.IsContrastPod(spec) { return spec } - if defaultRuntimeClass == "" && *spec.RuntimeClassName == "contrast-cc" { - collectedMissingRuntimes["contrast-cc"] = fmt.Errorf("no default platform was specified using --reference-values") - return spec - } - if !strings.HasPrefix(*spec.RuntimeClassName, "contrast-cc-") { + // Bare runtime class names (without hash suffix) are placeholders that + // get resolved during generate. They can't be parsed as platforms. + if *spec.RuntimeClassName == "contrast-cc" || *spec.RuntimeClassName == "contrast-insecure" { + if defaultRuntimeClass == "" { + collectedMissingRuntimes[*spec.RuntimeClassName] = fmt.Errorf("no default platform was specified using --reference-values") + } return spec } diff --git a/internal/kuberesource/mutators.go b/internal/kuberesource/mutators.go index 319394b4d08..e7a5638e5e9 100644 --- a/internal/kuberesource/mutators.go +++ b/internal/kuberesource/mutators.go @@ -26,7 +26,7 @@ import ( const CollateralProxyDefaultService = "http://collateral-proxy.default.svc" // contrastRuntimeClassPrefixes lists runtime class prefixes that identify Contrast pods. -var contrastRuntimeClassPrefixes = []string{"contrast-cc"} +var contrastRuntimeClassPrefixes = []string{"contrast-cc", "contrast-insecure"} // IsContrastPod reports whether a pod uses a Contrast runtime. func IsContrastPod(spec *applycorev1.PodSpecApplyConfiguration) bool { diff --git a/internal/kuberesource/runtimeclasses.go b/internal/kuberesource/runtimeclasses.go index 9020ad9ccd0..79047d79769 100644 --- a/internal/kuberesource/runtimeclasses.go +++ b/internal/kuberesource/runtimeclasses.go @@ -90,7 +90,12 @@ func (p PlatformCollection) AddFromResources(resources []any) error { for _, resource := range resources { _ = MapPodSpecWithMeta(resource, func(meta *applymetav1.ObjectMetaApplyConfiguration, spec *applycorev1.PodSpecApplyConfiguration, ) (*applymetav1.ObjectMetaApplyConfiguration, *applycorev1.PodSpecApplyConfiguration) { - if spec == nil || spec.RuntimeClassName == nil || !strings.HasPrefix(*spec.RuntimeClassName, "contrast-cc-") { + if !IsContrastPod(spec) { + return meta, spec + } + // Bare runtime class names (e.g. "contrast-cc") are placeholders + // that get resolved during generate. Skip them here. + if *spec.RuntimeClassName == "contrast-cc" || *spec.RuntimeClassName == "contrast-insecure" { return meta, spec } platform, err := platforms.FromRuntimeClassString(*spec.RuntimeClassName)