mirror of
https://github.com/containerd/containerd.git
synced 2026-11-06 13:27:16 +00:00
Image config labels are copied onto the container by both the CRI plugin (BuildLabels) and the client's WithImageConfigLabels option used by `ctr run`. Labels in the containerd.io/* namespace are interpreted by containerd itself and labels in the io.cri-containerd* namespace are interpreted by the CRI plugin. An image config is not a trusted source for labels in either namespace. Skip labels in both reserved namespaces when copying labels from an image config to a container, and warn about each label skipped: an image that tries to set them may be attempting to alter containerd behavior. Oversized image labels are already skipped this way by the CRI plugin. Labels set explicitly by clients, for example via `ctr run --label` or in the CRI request, are unaffected. Verified with the CRI plugin and with `ctr run` against an image whose config carries labels like these: the labels are no longer present on the created container and a warning is logged for each. Assisted-by: Claude Code Signed-off-by: Ben Cressey <ben@cressey.org> Signed-off-by: Samuel Karp <samuelkarp@google.com>
160 lines
5.2 KiB
Go
160 lines
5.2 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package util
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"maps"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
|
|
|
"github.com/containerd/containerd/v2/internal/cri/constants"
|
|
crilabels "github.com/containerd/containerd/v2/internal/cri/labels"
|
|
clabels "github.com/containerd/containerd/v2/pkg/labels"
|
|
"github.com/containerd/containerd/v2/pkg/namespaces"
|
|
"github.com/containerd/containerd/v2/pkg/timeout"
|
|
"github.com/containerd/errdefs"
|
|
"github.com/containerd/log"
|
|
"github.com/containerd/ttrpc"
|
|
)
|
|
|
|
// deferCleanupTimeoutKey is used to retrieve the configurable timeout for containerd cleanup operations in defer.
|
|
const (
|
|
deferCleanupTimeoutKey = "io.containerd.timeout.cri.defercleanup"
|
|
deferCleanupTimeout = 1 * time.Minute
|
|
)
|
|
|
|
func init() {
|
|
timeout.Set(deferCleanupTimeoutKey, deferCleanupTimeout)
|
|
}
|
|
|
|
// DeferContext returns a context for containerd cleanup operations in defer.
|
|
// A configurable default timeout is applied to avoid cleanup operation pending forever.
|
|
func DeferContext() (context.Context, context.CancelFunc) {
|
|
return context.WithTimeout(NamespacedContext(), timeout.Get(deferCleanupTimeoutKey))
|
|
}
|
|
|
|
// NamespacedContext returns a context with kubernetes namespace set.
|
|
func NamespacedContext() context.Context {
|
|
return WithNamespace(context.Background())
|
|
}
|
|
|
|
// WithNamespace adds kubernetes namespace to the context.
|
|
func WithNamespace(ctx context.Context) context.Context {
|
|
return namespaces.WithNamespace(ctx, constants.K8sContainerdNamespace)
|
|
}
|
|
|
|
// GetPassthroughAnnotations filters requested pod annotations by comparing
|
|
// against permitted annotations for the given runtime.
|
|
func GetPassthroughAnnotations(podAnnotations map[string]string,
|
|
runtimePodAnnotations []string) (passthroughAnnotations map[string]string) {
|
|
passthroughAnnotations = make(map[string]string)
|
|
|
|
for podAnnotationKey, podAnnotationValue := range podAnnotations {
|
|
for _, pattern := range runtimePodAnnotations {
|
|
// Use path.Match instead of filepath.Match here.
|
|
// filepath.Match treated `\\` as path separator
|
|
// on windows, which is not what we want.
|
|
if ok, _ := path.Match(pattern, podAnnotationKey); ok {
|
|
passthroughAnnotations[podAnnotationKey] = podAnnotationValue
|
|
}
|
|
}
|
|
}
|
|
return passthroughAnnotations
|
|
}
|
|
|
|
// BuildLabels builds the labels from config to be passed to containerd.
|
|
// Image config labels in the namespaces reserved for containerd
|
|
// (containerd.io/) and the CRI plugin (io.cri-containerd) are not copied
|
|
// to the container.
|
|
func BuildLabels(configLabels, imageConfigLabels map[string]string, containerType string) map[string]string {
|
|
labels := make(map[string]string)
|
|
|
|
for k, v := range imageConfigLabels {
|
|
// Labels in the containerd.io/* namespace are interpreted by containerd
|
|
// itself, and labels in the io.cri-containerd.* namespace are interpreted
|
|
// by the CRI plugin, so they are not copied from untrusted image configs.
|
|
if clabels.IsReserved(k) {
|
|
log.L.Warnf("skipping image label %q: the label namespace is reserved for containerd; possible malicious image attempting to alter containerd behavior", k)
|
|
continue
|
|
}
|
|
if err := clabels.Validate(k, v); err == nil {
|
|
labels[k] = v
|
|
} else {
|
|
// In case the image label is invalid, we output a warning and skip adding it to the
|
|
// container.
|
|
log.L.WithError(err).Warnf("unable to add image label with key %s to the container", k)
|
|
}
|
|
}
|
|
// labels from the CRI request (config) will override labels in the image config
|
|
maps.Copy(labels, configLabels)
|
|
labels[crilabels.ContainerKindLabel] = containerType
|
|
return labels
|
|
}
|
|
|
|
// GenerateUserString generates valid user string based on OCI Image Spec
|
|
// v1.0.0.
|
|
//
|
|
// CRI defines that the following combinations are valid:
|
|
//
|
|
// (none) -> ""
|
|
// username -> username
|
|
// username, uid -> username
|
|
// username, uid, gid -> username:gid
|
|
// username, gid -> username:gid
|
|
// uid -> uid
|
|
// uid, gid -> uid:gid
|
|
// gid -> error
|
|
//
|
|
// TODO(random-liu): Add group name support in CRI.
|
|
func GenerateUserString(username string, uid, gid *runtime.Int64Value) (string, error) {
|
|
var userstr, groupstr string
|
|
if uid != nil {
|
|
userstr = strconv.FormatInt(uid.GetValue(), 10)
|
|
}
|
|
if username != "" {
|
|
userstr = username
|
|
}
|
|
if gid != nil {
|
|
groupstr = strconv.FormatInt(gid.GetValue(), 10)
|
|
}
|
|
if userstr == "" {
|
|
if groupstr != "" {
|
|
return "", fmt.Errorf("user group %q is specified without user", groupstr)
|
|
}
|
|
return "", nil
|
|
}
|
|
if groupstr != "" {
|
|
userstr = userstr + ":" + groupstr
|
|
}
|
|
return userstr, nil
|
|
}
|
|
|
|
// IsShimTTRPCClosed returns true if the cause of error is ttrpc.ErrClosed from shim.
|
|
func IsShimTTRPCClosed(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
return errdefs.IsUnknown(err) && strings.HasSuffix(err.Error(), ttrpc.ErrClosed.Error())
|
|
}
|