-
Notifications
You must be signed in to change notification settings - Fork 238
Added a warning when using a non-Toolbx container #1707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8f169ee
ada55eb
d516223
e7bef55
dfaed70
c9215d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are invoking For example, here are some past commits where we optimized
The We can reduce the number of |
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decoding the JSON in this way can be fragile if the structure of the JSON changes, and over time Podman has sometimes done that. :(
See the comments in src/pkg/podman/container.go, and the
UnmarshalJSON()
method of the Image object.Fortunately, Go can decode JSON on a best effort basis, which can cover-up many of the superficial changes and needs a lot less code. Here's an introductory article about how it works. Sometimes, Go can't automatically handle the changes and we need to handle it ourselves, like in the above comments.
To take advantage of this, and to avoid invoking
podman inspect
repeatedly, we can decode the JSON into anImage
object that implements the json.Unmarshaler interface. It's implemented by thecontainerInspect
,containerPS
andImage
objects above. The interface will limit all manual interventions during decoding in one place.Unfortunately, the JSON used by Podman to represent images differs between
podman images
andpodman inspect --type image
. TheImage
object only handles the former, but not the latter.We have the same problem for containers. That's why we have a
Container
interface to hide the details, and it's implemented by the privatecontainerInspect
andcontainerPS
objects that actually decode the JSON.So, our first step would be to have a commit that introduces an
Image
interface, and renames the existingImage
object to make it private and makes it implement the interface. I don't know what would be the best name for the object, becauseimageImages
looks awkward, but it's also a private type so it won't be seen in too many places.Then, we have to add a second implementation of the
Image
interface that decodes the JSON frompodman inspect --type image
.Here are some commits that did the same thing for containers that you can use for help:
cmd, pkg/podman: Turn Container into an interface
cmd/run, pkg/podman: Make podman.InspectContainer() return a Container
Then, the
IsToolboxImage()
function can turn into theIsToolbx()
method of theImage
interface:cmd, pkg/podman: Turn IsToolboxContainer() into Container.IsToolbx()