diff --git a/cmd/preflight/cmd/list_checks.go b/cmd/preflight/cmd/list_checks.go index aed012ca..9c63a059 100644 --- a/cmd/preflight/cmd/list_checks.go +++ b/cmd/preflight/cmd/list_checks.go @@ -34,8 +34,10 @@ func printChecks(w io.Writer) { fmt.Fprintln(w, formattedPolicyBlock("Container", engine.ContainerPolicy(context.TODO()), "invoked on container images")) fmt.Fprintln(w, formattedPolicyBlock("Container Root Exception", engine.RootExceptionContainerPolicy(context.TODO()), "automatically applied for container images if preflight determines a root exception flag has been added to your Red Hat Connect project")) - fmt.Fprintln(w, formattedPolicyBlock("Container Scratch Exception", engine.ScratchContainerPolicy(context.TODO()), + fmt.Fprintln(w, formattedPolicyBlock("Container Scratch (NonRoot) Exception", engine.ScratchNonRootContainerPolicy(context.TODO()), "automatically applied for container checks if preflight determines a scratch exception flag has been added to your Red Hat Connect project")) + fmt.Fprintln(w, formattedPolicyBlock("Container Scratch (Root) Exception", engine.ScratchRootContainerPolicy(context.TODO()), + "automatically applied for container checks if preflight determines scratch and root exception flags have both been added to your Red Hat Connect project")) } // formattedPolicyBlock accepts information about the checklist diff --git a/cmd/preflight/cmd/list_checks_test.go b/cmd/preflight/cmd/list_checks_test.go index bcd6c428..ba8b6499 100644 --- a/cmd/preflight/cmd/list_checks_test.go +++ b/cmd/preflight/cmd/list_checks_test.go @@ -65,7 +65,7 @@ var _ = Describe("list checks subcommand", func() { }) It("should always contain the scratch exception policy", func() { - expected := formatList(engine.ScratchContainerPolicy(context.TODO())) + expected := formatList(engine.ScratchNonRootContainerPolicy(context.TODO())) buf := strings.Builder{} printChecks(&buf) diff --git a/container/check_container.go b/container/check_container.go index fd8bdeee..eafab42a 100644 --- a/container/check_container.go +++ b/container/check_container.go @@ -46,7 +46,7 @@ func (c *containerCheck) Run(ctx context.Context) (certification.Results, error) cfg := runtime.Config{ Image: c.image, DockerConfig: c.dockerconfigjson, - Scratch: c.policy == policy.PolicyScratch, + Scratch: c.policy == policy.PolicyScratchNonRoot || c.policy == policy.PolicyScratchRoot, Bundle: false, Insecure: c.insecure, Platform: c.platform, diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 7dba4e59..452e8194 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -784,7 +784,7 @@ func InitializeContainerChecks(ctx context.Context, p policy.Policy, cfg Contain cfg.CertificationProjectID, &http.Client{Timeout: 60 * time.Second})), }, nil - case policy.PolicyScratch: + case policy.PolicyScratchNonRoot: return []check.Check{ &containerpol.HasLicenseCheck{}, containerpol.NewHasUniqueTagCheck(cfg.DockerConfig), @@ -792,6 +792,13 @@ func InitializeContainerChecks(ctx context.Context, p policy.Policy, cfg Contain &containerpol.HasRequiredLabelsCheck{}, &containerpol.RunAsNonRootCheck{}, }, nil + case policy.PolicyScratchRoot: + return []check.Check{ + &containerpol.HasLicenseCheck{}, + containerpol.NewHasUniqueTagCheck(cfg.DockerConfig), + &containerpol.MaxLayersCheck{}, + &containerpol.HasRequiredLabelsCheck{}, + }, nil } return nil, fmt.Errorf("provided container policy %s is unknown", p) @@ -812,7 +819,7 @@ func makeCheckList(checks []check.Check) []string { func checkNamesFor(ctx context.Context, p policy.Policy) []string { var c []check.Check switch p { - case policy.PolicyContainer, policy.PolicyRoot, policy.PolicyScratch: + case policy.PolicyContainer, policy.PolicyRoot, policy.PolicyScratchNonRoot, policy.PolicyScratchRoot: c, _ = InitializeContainerChecks(ctx, p, ContainerCheckConfig{}) case policy.PolicyOperator: c, _ = InitializeOperatorChecks(ctx, p, OperatorCheckConfig{}) @@ -833,10 +840,16 @@ func ContainerPolicy(ctx context.Context) []string { return checkNamesFor(ctx, policy.PolicyContainer) } -// ScratchContainerPolicy returns the names of checks in the +// ScratchNonRootContainerPolicy returns the names of checks in the // container policy with scratch exception. -func ScratchContainerPolicy(ctx context.Context) []string { - return checkNamesFor(ctx, policy.PolicyScratch) +func ScratchNonRootContainerPolicy(ctx context.Context) []string { + return checkNamesFor(ctx, policy.PolicyScratchNonRoot) +} + +// ScratchRootContainerPolicy returns the names of checks in the +// container policy with scratch and root exception. +func ScratchRootContainerPolicy(ctx context.Context) []string { + return checkNamesFor(ctx, policy.PolicyScratchRoot) } // RootExceptionContainerPolicy returns the names of checks in the diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go index 834b0ccb..eb54129a 100644 --- a/internal/engine/engine_test.go +++ b/internal/engine/engine_test.go @@ -300,7 +300,11 @@ var _ = Describe("Check Initialization", func() { Expect(err).ToNot(HaveOccurred()) }) It("should properly return checks for the scratch policy", func() { - _, err := InitializeContainerChecks(context.TODO(), policy.PolicyScratch, ContainerCheckConfig{}) + _, err := InitializeContainerChecks(context.TODO(), policy.PolicyScratchNonRoot, ContainerCheckConfig{}) + Expect(err).ToNot(HaveOccurred()) + }) + It("should properly return checks for the scratch and root policy", func() { + _, err := InitializeContainerChecks(context.TODO(), policy.PolicyScratchRoot, ContainerCheckConfig{}) Expect(err).ToNot(HaveOccurred()) }) It("should properly return checks for the root policy", func() { @@ -353,13 +357,19 @@ var _ = Describe("Check Name Queries", func() { "FollowsRestrictedNetworkEnablementGuidelines", "RequiredAnnotations", }), - Entry("scratch container policy", ScratchContainerPolicy, []string{ + Entry("scratch container policy", ScratchNonRootContainerPolicy, []string{ "HasLicense", "HasUniqueTag", "LayerCountAcceptable", "HasRequiredLabel", "RunAsNonRoot", }), + Entry("scratch container policy", ScratchRootContainerPolicy, []string{ + "HasLicense", + "HasUniqueTag", + "LayerCountAcceptable", + "HasRequiredLabel", + }), Entry("root container policy", RootExceptionContainerPolicy, []string{ "HasLicense", "HasUniqueTag", diff --git a/internal/lib/fakes_test.go b/internal/lib/fakes_test.go index 4d2a17f2..3e8353af 100644 --- a/internal/lib/fakes_test.go +++ b/internal/lib/fakes_test.go @@ -157,6 +157,17 @@ func gpFuncReturnRootException(ctx context.Context) (*pyxis.CertProject, error) }, nil } +// gpFuncReturnScratchRootException implements gpFunc and returns a root exception. +func gpFuncReturnScratchRootException(ctx context.Context) (*pyxis.CertProject, error) { + return &pyxis.CertProject{ + Container: pyxis.Container{ + DockerConfigJSON: "", + OsContentType: "Scratch Image", + Privileged: true, + }, + }, nil +} + // gpFuncReturnNoException implements gpFunc and returns no exception indicators. func gpFuncReturnNoException(ctx context.Context) (*pyxis.CertProject, error) { return &pyxis.CertProject{ diff --git a/internal/lib/lib.go b/internal/lib/lib.go index 1fa98845..02027364 100644 --- a/internal/lib/lib.go +++ b/internal/lib/lib.go @@ -39,8 +39,17 @@ func GetContainerPolicyExceptions(ctx context.Context, pc PyxisClient) (policy.P return "", fmt.Errorf("could not retrieve project: %w", err) } logger.V(log.DBG).Info("certification project", "name", certProject.Name) + + // if the partner has gotten a scratch exception from the business and os_content_type == "Scratch Image" + // and a partner sets `Host Level Access` in connect to `Privileged`, enable ScratchRootContainerPolicy checks + if certProject.ScratchProject() && certProject.Container.Privileged { + return policy.PolicyScratchRoot, nil + } + + // if the partner has gotten a scratch exception from the business and os_content_type == "Scratch Image", + // enable ScratchNonRootContainerPolicy checks if certProject.ScratchProject() { - return policy.PolicyScratch, nil + return policy.PolicyScratchNonRoot, nil } // if a partner sets `Host Level Access` in connect to `Privileged`, enable RootExceptionContainerPolicy checks diff --git a/internal/lib/types.go b/internal/lib/types.go index 2ea6c25e..2481ca6c 100644 --- a/internal/lib/types.go +++ b/internal/lib/types.go @@ -161,12 +161,12 @@ func (s *ContainerCertificationSubmitter) Submit(ctx context.Context) error { pol := policy.PolicyContainer if certProject.ScratchProject() { - pol = policy.PolicyScratch + pol = policy.PolicyScratchNonRoot } // only read the rpm manifest file off of disk if the policy executed is not scratch // scratch images do not have rpm manifests, the rpm-manifest.json file is not written to disk by the engine during execution - if pol != policy.PolicyScratch { + if pol != policy.PolicyScratchNonRoot { rpmManifest, err := os.Open(path.Join(artifactWriter.Path(), check.DefaultRPMManifestFilename)) if err != nil { return fmt.Errorf( diff --git a/internal/lib/types_test.go b/internal/lib/types_test.go index ac81df67..46de4fa2 100644 --- a/internal/lib/types_test.go +++ b/internal/lib/types_test.go @@ -74,14 +74,14 @@ var _ = Describe("Policy Resolution", func() { It("should return a scratch policy exception if the project has type flag in the API", func() { fakePC.getProjectsFunc = gpFuncReturnScratchException p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) - Expect(p).To(Equal(policy.PolicyScratch)) + Expect(p).To(Equal(policy.PolicyScratchNonRoot)) Expect(err).ToNot(HaveOccurred()) }) It("should return a scratch policy exception if the project has os_content_type flag in the API", func() { fakePC.getProjectsFunc = gpFuncReturnScratchImageException p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) - Expect(p).To(Equal(policy.PolicyScratch)) + Expect(p).To(Equal(policy.PolicyScratchNonRoot)) Expect(err).ToNot(HaveOccurred()) }) @@ -92,6 +92,13 @@ var _ = Describe("Policy Resolution", func() { Expect(err).ToNot(HaveOccurred()) }) + It("should return a scratch plus root policy exception if the project has the flag in the API", func() { + fakePC.getProjectsFunc = gpFuncReturnScratchRootException + p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) + Expect(p).To(Equal(policy.PolicyScratchRoot)) + Expect(err).ToNot(HaveOccurred()) + }) + It("should return a container policy exception if the project no exceptions in the API", func() { fakePC.getProjectsFunc = gpFuncReturnNoException p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) diff --git a/internal/policy/policy.go b/internal/policy/policy.go index 8f2d3e41..06c89514 100644 --- a/internal/policy/policy.go +++ b/internal/policy/policy.go @@ -3,8 +3,9 @@ package policy type Policy = string const ( - PolicyOperator Policy = "operator" - PolicyContainer Policy = "container" - PolicyScratch Policy = "scratch" - PolicyRoot Policy = "root" + PolicyOperator Policy = "operator" + PolicyContainer Policy = "container" + PolicyScratchNonRoot Policy = "scratch-nonroot" + PolicyScratchRoot Policy = "scratch-root" + PolicyRoot Policy = "root" ) diff --git a/test/containerfiles/scratch-root-passes.Dockerfile b/test/containerfiles/scratch-root-passes.Dockerfile new file mode 100644 index 00000000..c75f35d1 --- /dev/null +++ b/test/containerfiles/scratch-root-passes.Dockerfile @@ -0,0 +1,12 @@ +FROM scratch + +COPY example-license.txt /licenses/ + +LABEL name="preflight test image scratch plus root container-policy" \ + vendor="preflight test vendor" \ + version="1" \ + release="1" \ + summary="testing the preflight tool" \ + description="test the preflight tool" + +USER root