diff --git a/src/cmd/create.go b/src/cmd/create.go index 531721b60..6de0ac4b0 100644 --- a/src/cmd/create.go +++ b/src/cmd/create.go @@ -241,6 +241,18 @@ func createContainer(container, image, release, authFile string, showCommandToEn } } + isImageCompatible, warningMessage, err := podman.DoesImageFulfillRequirements(imageFull) + if err != nil { + return fmt.Errorf("%w", err) + } + + if !isImageCompatible { + fmt.Fprintf(os.Stderr, "%s\n", warningMessage) + if !rootFlags.assumeYes && !askForConfirmation("One or more of the image's requirements are not met. Continue anyway? [y/N]:") { + return nil + } + } + var toolbxDelayEntryPointEnv []string if toolbxDelayEntryPoint, ok := os.LookupEnv("TOOLBX_DELAY_ENTRY_POINT"); ok { diff --git a/src/cmd/rmi.go b/src/cmd/rmi.go index f10b33da2..50bcffef3 100644 --- a/src/cmd/rmi.go +++ b/src/cmd/rmi.go @@ -90,9 +90,12 @@ func rmi(cmd *cobra.Command, args []string) error { } for _, image := range args { - if _, err := podman.IsToolboxImage(image); err != nil { + if isToolboxImage, err := podman.IsToolboxImage(image); err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) continue + } else if !isToolboxImage { + fmt.Fprintf(os.Stderr, "Error: %s is not a Toolbx image\n", image) + continue } if err := podman.RemoveImage(image, rmiFlags.forceDelete); err != nil { diff --git a/src/cmd/run.go b/src/cmd/run.go index 389ea1615..fbc04e034 100644 --- a/src/cmd/run.go +++ b/src/cmd/run.go @@ -183,6 +183,8 @@ func runCommand(container string, } } + checkImageCompatibility := true + logrus.Debugf("Checking if container %s exists", container) if _, err := podman.ContainerExists(container); err != nil { @@ -225,6 +227,10 @@ func runCommand(container string, if err := createContainer(container, image, release, "", false); err != nil { return err } + + // set to false -> check was already made when creating container during toolbx enter + checkImageCompatibility = false + } else if containersCount == 1 && defaultContainer { fmt.Fprintf(os.Stderr, "Error: container %s not found\n", container) @@ -249,6 +255,18 @@ func runCommand(container string, return fmt.Errorf("failed to inspect container %s", container) } + isImageCompatible, warningMessage, err := podman.DoesImageFulfillRequirements(containerObj.Image()) + if err != nil { + return fmt.Errorf("%w", err) + } + + if !isImageCompatible && checkImageCompatibility { + fmt.Fprintf(os.Stderr, "%s\n", warningMessage) + if !rootFlags.assumeYes && !askForConfirmation("One or more of the image's requirements are not met. Continue anyway? [y/N]:") { + return nil + } + } + entryPoint := containerObj.EntryPoint() entryPointPID := containerObj.EntryPointPID() logrus.Debugf("Entry point of container %s is %s (PID=%d)", container, entryPoint, entryPointPID) diff --git a/src/pkg/podman/podman.go b/src/pkg/podman/podman.go index 4711b8b5c..643b4bbad 100644 --- a/src/pkg/podman/podman.go +++ b/src/pkg/podman/podman.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "strconv" + "strings" "time" "github.com/HarryMichal/go-version" @@ -352,17 +353,84 @@ func IsToolboxImage(image string) (bool, error) { } if info["Labels"] == nil { - return false, fmt.Errorf("%s is not a Toolbx image", image) + return false, nil } labels := info["Labels"].(map[string]interface{}) if labels["com.github.containers.toolbox"] != "true" && labels["com.github.debarshiray.toolbox"] != "true" { - return false, fmt.Errorf("%s is not a Toolbx image", image) + return false, nil } return true, nil } +func IsLDPRELOADEnvSet(image string) (bool, error) { + info, err := InspectImage(image) + if err != nil { + return false, fmt.Errorf("failed to inspect image %s: %s", image, err) + } + + if info["Config"] == nil { + return false, nil + } + + config := info["Config"].(map[string]interface{}) + if config["Env"] == nil { + return false, nil + } + + env := config["Env"] + switch envVars := env.(type) { + case []interface{}: + for _, envVar := range envVars { + if envVarStr, ok := envVar.(string); ok { + envVarStrTrimmed := strings.TrimSpace(envVarStr) + if strings.HasPrefix(envVarStrTrimmed, "LD_PRELOAD=") { + return true, nil + } + } + } + case []string: + for _, envVar := range envVars { + envVarTrimmed := strings.TrimSpace(envVar) + if strings.HasPrefix(envVarTrimmed, "LD_PRELOAD=") { + return true, nil + } + } + default: + return false, fmt.Errorf("unexpected type '%T' of environment variables in image %s", env, image) + } + + return false, nil +} + +func HasImageEntrypoint(image string) (bool, error) { + info, err := InspectImage(image) + if err != nil { + return false, fmt.Errorf("failed to inspect image %s: %s", image, err) + } + + if info["Config"] == nil { + return false, nil + } + + config := info["Config"].(map[string]interface{}) + if config["Entrypoint"] == nil { + return false, nil + } + + entrypoint := config["Entrypoint"] + + switch ep := entrypoint.(type) { + case []interface{}: + return len(ep) > 0, nil + case []string: + return len(ep) > 0, nil + default: + return false, fmt.Errorf("unexpected type '%T' of entrypoint of image %s", entrypoint, image) + } +} + func Logs(container string, since time.Time, stderr io.Writer) error { ctx := context.Background() err := LogsContext(ctx, container, false, since, stderr) @@ -506,3 +574,38 @@ func SystemMigrate(ociRuntimeRequired string) error { return nil } + +func DoesImageFulfillRequirements(image string) (bool, string, error) { + var warnings []string + + isToolboxImage, err := IsToolboxImage(image) + if err != nil { + return false, "", fmt.Errorf("failed to verify image compatibility: %w", err) + } + if !isToolboxImage { + warnings = append(warnings, fmt.Sprintf("Warning: Image '%s' does not contain either of the labels 'com.github.containers.toolbox=true' and 'com.github.debarshiray.toolbox=true'", image)) + } + + isLDPRELOADEnvSet, err := IsLDPRELOADEnvSet(image) + if err != nil { + return false, "", fmt.Errorf("failed to validate LD_PRELOAD variable settings: %w", err) + } + if isLDPRELOADEnvSet { + warnings = append(warnings, fmt.Sprintf("Warning: Image '%s' has environment variable LD_PRELOAD set, which may cause container vulnerability (Container Escape)", image)) + } + + hasEntrypoint, err := HasImageEntrypoint(image) + if err != nil { + return false, "", fmt.Errorf("failed to check image entrypoint: %w", err) + } + if hasEntrypoint { + warnings = append(warnings, fmt.Sprintf("Warning: Image '%s' has an entrypoint defined", image)) + } + + if len(warnings) > 0 { + warningMessage := strings.Join(warnings, "\n") + return false, warningMessage, nil + } + + return true, "", nil +} diff --git a/test/system/101-create.bats b/test/system/101-create.bats index db7c7651e..a2d99a1fc 100644 --- a/test/system/101-create.bats +++ b/test/system/101-create.bats @@ -1009,3 +1009,257 @@ teardown() { assert [ ${#lines[@]} -eq 2 ] assert [ ${#stderr_lines[@]} -eq 0 ] } + +@test "create: With a non-Toolbx image and prompt for confirmation - Yes" { + image="$(build_non_toolbx_image)" + containerName="test-container-non-toolbx" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "y" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With a non-Toolbx image and prompt for confirmation - No" { + image="$(build_non_toolbx_image)" + containerName="test-container-non-toolbx" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert [ ${#lines[@]} -eq 1 ] +} + +@test "create: With a non-Toolbx image and prompt for confirmation - assumeyes" { + image="$(build_non_toolbx_image)" + containerName="test-container-non-toolbx" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --assumeyes --image "$image" "$containerName" + + assert_success + assert_line --index 0 "$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With an image with LD_PRELOAD set and prompt for confirmation - Yes" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "y" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With an image with LD_PRELOAD set and prompt for confirmation - No" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert [ ${#lines[@]} -eq 1 ] +} + +@test "create: With an image with LD_PRELOAD set and prompt for confirmation - assumeyes" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create --image "$image" "$containerName" + + assert_success + assert_line --index 0 "$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With an image with an entrypoint set and prompt for confirmation - Yes" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "y" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With an image with an entrypoint set and prompt for confirmation - No" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert [ ${#lines[@]} -eq 1 ] +} + +@test "create: With an image with an entrypoint set and prompt for confirmation - assumeyes" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes create --image "$image" "$containerName" + + assert_success + assert_line --index 0 "$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With an image having all warnings and prompt for confirmation - Yes" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "y" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 3 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} + +@test "create: With an image having all warnings and prompt for confirmation - No" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --image "$image" "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 3 ] + + run podman ps --all + + assert_success + assert [ ${#lines[@]} -eq 1 ] +} + +@test "create: With an image having all warnings and prompt for confirmation - assumeyes" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + run --keep-empty-lines --separate-stderr "$TOOLBX" create --assumeyes --image "$image" "$containerName" + + assert_success + assert_line --index 0 "$(created_container_message "$containerName")" + assert_line --index 1 "$(enter_with_message "$containerName")" + assert [ ${#lines[@]} -eq 2 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 3 ] + + run podman ps --all + + assert_success + assert_output --regexp "Created[[:blank:]]+$containerName" +} diff --git a/test/system/104-run.bats b/test/system/104-run.bats index 3883deeb5..245481d97 100644 --- a/test/system/104-run.bats +++ b/test/system/104-run.bats @@ -863,3 +863,217 @@ teardown() { assert_line --index 1 "Recreate it with Toolbx version 0.0.17 or newer." assert [ ${#stderr_lines[@]} -eq 2 ] } + +@test "run: With a non-Toolbx image and prompt for confirmation - Yes" { + containerName="test-container-non-toolbx" + image="$(build_non_toolbx_image)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: With a non-Toolbx image and prompt for confirmation - No" { + containerName="test-container-non-toolbx" + image="$(build_non_toolbx_image)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "run: With a non-Toolbx image and prompt for confirmation - assumeyes" { + containerName="test-container-non-toolbx" + image="$(build_non_toolbx_image)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes run --container "$containerName" true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: With an image with LD_PRELOAD set and prompt for confirmation - Yes" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: With an image with LD_PRELOAD set and prompt for confirmation - No" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "run: With an image with LD_PRELOAD set and prompt for confirmation - assumeyes" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes run --container "$containerName" true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: With an image with an entrypoint set and prompt for confirmation - Yes" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: With an image with an entrypoint set and prompt for confirmation - No" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "run: With an image with an entrypoint set and prompt for confirmation - assumeyes" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes run --container "$containerName" true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "run: With an image having all warnings and prompt for confirmation - Yes" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert_line --index 3 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 4 ] +} + +@test "run: With an image having all warnings and prompt for confirmation - No" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" run --container "$containerName" true <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 3 ] +} + +@test "run: With an image having all warnings and prompt for confirmation - assumeyes" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes run --container "$containerName" true + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert_line --index 3 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 4 ] +} diff --git a/test/system/105-enter.bats b/test/system/105-enter.bats index 6ff900e47..7eef5bc54 100644 --- a/test/system/105-enter.bats +++ b/test/system/105-enter.bats @@ -147,6 +147,220 @@ teardown() { assert [ ${#lines[@]} -eq 3 ] } +@test "enter: With a non-Toolbx image and prompt for confirmation - Yes" { + containerName="test-container-non-toolbx" + image="$(build_non_toolbx_image)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "enter: With a non-Toolbx image and prompt for confirmation - No" { + containerName="test-container-non-toolbx" + image="$(build_non_toolbx_image)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "enter: With a non-Toolbx image and prompt for confirmation - assumeyes" { + containerName="test-container-non-toolbx" + image="$(build_non_toolbx_image)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes enter --container "$containerName" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "enter: With an image with LD_PRELOAD set and prompt for confirmation - Yes" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "enter: With an image with LD_PRELOAD set and prompt for confirmation - No" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "enter: With an image with LD_PRELOAD set and prompt for confirmation - assumeyes" { + containerName="test-container-ld-preload" + image="$(build_image_with_ld_preload)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes enter --container "$containerName" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_ld_preload_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "enter: With an image with an entrypoint set and prompt for confirmation - Yes" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "enter: With an image with an entrypoint set and prompt for confirmation - No" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 1 ] +} + +@test "enter: With an image with an entrypoint set and prompt for confirmation - assumeyes" { + containerName="test-container-entrypoint" + image="$(build_image_with_entrypoint)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes enter --container "$containerName" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_entrypoint_image "$image")" + assert_line --index 1 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 2 ] +} + +@test "enter: With an image having all warnings and prompt for confirmation - Yes" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "y" + + assert_failure + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert_line --index 3 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 4 ] +} + +@test "enter: With an image having all warnings and prompt for confirmation - No" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" enter --container "$containerName" <<< "n" + + assert_success + assert_line --index 0 "${MSG_CONFIRMATION_PROMPT}" + assert [ ${#lines[@]} -eq 1 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert [ ${#stderr_lines[@]} -eq 3 ] +} + +@test "enter: With an image having all warnings and prompt for confirmation - assumeyes" { + containerName="test-container-all-warnings" + image="$(build_image_with_all_warnings)" + + create_image_container "$image" "$containerName" + + run --keep-empty-lines --separate-stderr "$TOOLBX" --assumeyes enter --container "$containerName" + + assert_failure + assert [ ${#lines[@]} -eq 0 ] + + lines=("${stderr_lines[@]}") + assert_line --index 0 "$(warning_non_toolbx_image "$image")" + assert_line --index 1 "$(warning_ld_preload_image "$image")" + assert_line --index 2 "$(warning_entrypoint_image "$image")" + assert_line --index 3 "$(failed_start_error_message "$containerName")" + assert [ ${#stderr_lines[@]} -eq 4 ] +} + # TODO: Write the test @test "enter: Enter the default Toolbx" { skip "Testing of entering Toolbxes is not implemented" diff --git a/test/system/libs/helpers.bash b/test/system/libs/helpers.bash index 33b42eaf6..4ed593f3a 100644 --- a/test/system/libs/helpers.bash +++ b/test/system/libs/helpers.bash @@ -39,6 +39,42 @@ readonly TOOLBX="${TOOLBX:-$(command -v toolbox)}" readonly TOOLBX_TEST_SYSTEM_TAGS_ALL="arch-fedora,commands-options,custom-image,runtime-environment,ubuntu" readonly TOOLBX_TEST_SYSTEM_TAGS="${TOOLBX_TEST_SYSTEM_TAGS:-$TOOLBX_TEST_SYSTEM_TAGS_ALL}" + +# Common test messages (used across multiple test suites) +export MSG_CONFIRMATION_PROMPT="One or more of the image's requirements are not met. Continue anyway? [y/N]: " + +# Helper functions for generating common messages +function created_container_message() { + local container_name="$1" + echo "Created container: $container_name" +} + +function enter_with_message() { + local container_name="$1" + echo "Enter with: toolbox enter $container_name" +} + +function failed_start_error_message() { + local container_name="$1" + echo "Error: failed to start container $container_name" +} + +# Helper functions for generating image warning messages +function warning_non_toolbx_image() { + local image="$1" + echo "Warning: Image '$image' does not contain either of the labels 'com.github.containers.toolbox=true' and 'com.github.debarshiray.toolbox=true'" +} + +function warning_ld_preload_image() { + local image="$1" + echo "Warning: Image '$image' has environment variable LD_PRELOAD set, which may cause container vulnerability (Container Escape)" +} + +function warning_entrypoint_image() { + local image="$1" + echo "Warning: Image '$image' has an entrypoint defined" +} + # Images declare -Ag IMAGES=([arch]="quay.io/toolbx/arch-toolbox" \ [busybox]="quay.io/toolbox_tests/busybox" \ @@ -255,6 +291,57 @@ function build_image_without_name() { } +# Generic helper function to build test images with custom Containerfile content +# +# Parameters +# ========== +# - image_name - Used as part of the image name +# - containerfile_content - Complete Containerfile content to build the image +# +# The function creates a temporary Containerfile with the provided content, +# builds the image using Podman, and returns the full image name that was built. +function _build_test_image_generic() { + local image_name="$1" + local containerfile_content="$2" + + local image_name_full="localhost/${image_name}:test-$$" + + echo -e "$containerfile_content" > "$BATS_TEST_TMPDIR"/Containerfile + + run podman build --quiet --tag "$image_name_full" "$BATS_TEST_TMPDIR" + + assert_success + assert_line --index 0 --regexp "^[a-f0-9]{64}$" + + # shellcheck disable=SC2154 + assert [ ${#lines[@]} -eq 1 ] + + rm -f "$BATS_TEST_TMPDIR"/Containerfile + + echo "$image_name_full" +} + + +function build_non_toolbx_image() { + _build_test_image_generic "non-toolbx" "FROM scratch\n\nLABEL test=\"non-toolbx\"" +} + + +function build_image_with_ld_preload() { + _build_test_image_generic "ld-preload" "FROM scratch\n\nENV LD_PRELOAD=foobar.so\n\nLABEL com.github.containers.toolbox=true" +} + + +function build_image_with_entrypoint() { + _build_test_image_generic "entrypoint" "FROM scratch\n\nENTRYPOINT [\"/bin/sh\", \"-c\", \"echo 'Hello, World!'\"]\n\nLABEL com.github.containers.toolbox=true" +} + + +function build_image_with_all_warnings() { + _build_test_image_generic "all-warnings" "FROM scratch\n\nENV LD_PRELOAD=foobar.so\n\nENTRYPOINT [\"/bin/sh\", \"-c\", \"echo 'Multiple warnings!'\"]\n\nLABEL test=\"all-warnings\"" +} + + function check_bats_version() { local required_version required_version="$1" @@ -422,6 +509,24 @@ function create_default_container() { } +# Creates a container with specific name and image +# +# Parameters: +# =========== +# - image - name of the image +# - container_name - name of the container +function create_image_container() { + local image + local container_name + + image="$1" + container_name="$2" + + "$TOOLBX" --assumeyes create --container "${container_name}" --image "${image}" >/dev/null \ + || fail "Toolbx couldn't create container '$container_name'" +} + + function start_container() { local container_name container_name="$1"