-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Confidential Containers - Skip pullimage for runtimes that are handling it #8008
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
b3defba
f5a5678
555607f
4226c2c
0975645
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 |
|---|---|---|
|
|
@@ -11,9 +11,11 @@ | |
| "time" | ||
|
|
||
| "github.com/containers/image/v5/docker/reference" | ||
| "github.com/containers/image/v5/image" | ||
| "github.com/containers/image/v5/manifest" | ||
| "github.com/containers/image/v5/pkg/blobinfocache" | ||
| "github.com/containers/image/v5/types" | ||
| v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||
|
|
||
| "github.com/cri-o/cri-o/internal/log" | ||
| ) | ||
|
|
@@ -84,37 +86,14 @@ | |
| opts = &PullOptions{} | ||
| } | ||
|
|
||
| name, err := o.impl.ParseNormalizedNamed(img) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parse image name: %w", err) | ||
| } | ||
| name = reference.TagNameOnly(name) // make sure to add ":latest" if needed | ||
|
|
||
| ref, err := o.impl.NewReference(name) | ||
| src, err := o.getSourceFromImageName(ctx, img, opts) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("create docker reference: %w", err) | ||
| return nil, err | ||
| } | ||
|
|
||
| src, err := o.impl.NewImageSource(ctx, ref, opts.SystemContext) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("build image source: %w", err) | ||
| } | ||
|
|
||
| manifestBytes, mimeType, err := o.impl.GetManifest(ctx, src, nil) | ||
| parsedManifest, err := o.getParsedManifest(ctx, src, opts) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get manifest: %w", err) | ||
| } | ||
|
|
||
| parsedManifest, err := o.impl.ManifestFromBlob(manifestBytes, mimeType) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parse manifest: %w", err) | ||
| } | ||
| if opts.EnforceConfigMediaType != "" && o.impl.ManifestConfigInfo(parsedManifest).MediaType != opts.EnforceConfigMediaType { | ||
| return nil, fmt.Errorf( | ||
| "wrong config media type %q, requires %q", | ||
| o.impl.ManifestConfigInfo(parsedManifest).MediaType, | ||
| opts.EnforceConfigMediaType, | ||
| ) | ||
| return nil, err | ||
| } | ||
|
|
||
| layers := o.impl.LayerInfos(parsedManifest) | ||
|
|
@@ -164,6 +143,130 @@ | |
| }, nil | ||
| } | ||
|
|
||
| func (o *OCIArtifact) GetManifest(ctx context.Context, img string, opts *PullOptions) (manifest.Manifest, error) { | ||
| log.Infof(ctx, "Pulling manifest from ref: %s", img) | ||
|
|
||
| // Use default pull options | ||
| if opts == nil { | ||
| opts = &PullOptions{} | ||
| } | ||
|
|
||
| src, err := o.getSourceFromImageName(ctx, img, opts) | ||
|
Collaborator
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. Creating an Overall, the way this file has been refactored seems mostly misguided to me; it introduces abstraction boundaries in wrong places. |
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| parsedManifest, err := o.getParsedManifest(ctx, src, opts) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return parsedManifest, nil | ||
| } | ||
|
|
||
| func (o *OCIArtifact) GetConfig(ctx context.Context, img string, opts *PullOptions) (*v1.Image, error) { | ||
|
Collaborator
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. In general, artifacts don’t have image configs. If they have image configs, they are images. (I can see a semantic argument that “image is an artifact”, but… what are we doing here?) |
||
| log.Infof(ctx, "Getting config from ref: %s", img) | ||
|
|
||
| // Use default pull options | ||
| if opts == nil { | ||
| opts = &PullOptions{} | ||
| } | ||
|
|
||
| src, err := o.getSourceFromImageName(ctx, img, opts) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| unparsedToplevel := image.UnparsedInstance(src, nil) | ||
| topManifest, topMIMEType, err := unparsedToplevel.Manifest(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get manifest: %w", err) | ||
| } | ||
|
|
||
| unparsedInstance := unparsedToplevel | ||
| if manifest.MIMETypeIsMultiImage(topMIMEType) { | ||
|
Collaborator
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. It is pretty surprising for This is both costly (fetching manifests twice or more), and risks that the code will diverge (… as it seems to already have done). |
||
| // This is a manifest list. We need to choose a single instance to work with. | ||
| manifestList, err := manifest.ListFromBlob(topManifest, topMIMEType) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parsing primary manifest as list: %w", err) | ||
| } | ||
| instanceDigest, err := manifestList.ChooseInstance(opts.SystemContext) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("choosing an image from manifest list: %w", err) | ||
| } | ||
|
|
||
| unparsedInstance = image.UnparsedInstance(src, &instanceDigest) | ||
| } | ||
|
|
||
| sourcedImage, err := image.FromUnparsedImage(ctx, opts.SystemContext, unparsedInstance) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("getting sourced image from unparsed image: %w", err) | ||
| } | ||
| return sourcedImage.OCIConfig(ctx) | ||
| } | ||
|
|
||
| func (o *OCIArtifact) getSourceFromImageName(ctx context.Context, img string, opts *PullOptions) (types.ImageSource, error) { | ||
| name, err := o.impl.ParseNormalizedNamed(img) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parse image name: %w", err) | ||
| } | ||
| name = reference.TagNameOnly(name) // make sure to add ":latest" if needed | ||
|
|
||
| ref, err := o.impl.NewReference(name) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("create docker reference: %w", err) | ||
| } | ||
|
|
||
| src, err := o.impl.NewImageSource(ctx, ref, opts.SystemContext) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("build image source: %w", err) | ||
| } | ||
| return src, nil | ||
| } | ||
|
|
||
| func (o *OCIArtifact) getParsedManifest(ctx context.Context, src types.ImageSource, opts *PullOptions) (manifest.Manifest, error) { | ||
| manifestBytes, mimeType, err := o.impl.GetManifest(ctx, src, nil) | ||
|
Collaborator
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.
Use |
||
| if err != nil { | ||
| return nil, fmt.Errorf("get manifest: %w", err) | ||
| } | ||
|
|
||
| if manifest.MIMETypeIsMultiImage(mimeType) { | ||
| // This is a manifest list. We need to choose a single instance to work with. | ||
| manifestList, err := manifest.ListFromBlob(manifestBytes, mimeType) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parsing primary manifest as list: %w", err) | ||
| } | ||
| instanceDigest, err := manifestList.ChooseInstance(opts.SystemContext) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("choosing an image from manifest list: %w", err) | ||
| } | ||
|
|
||
| unparsedInstance := image.UnparsedInstance(src, &instanceDigest) | ||
|
|
||
| sourcedImage, err := image.FromUnparsedImage(ctx, opts.SystemContext, unparsedInstance) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("getting sourced image from unparsed image: %w", err) | ||
| } | ||
| manifestBytes, mimeType, err = sourcedImage.Manifest(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("getting manifest bytes from sourced image: %w", err) | ||
| } | ||
| } | ||
|
|
||
| parsedManifest, err := o.impl.ManifestFromBlob(manifestBytes, mimeType) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parse manifest: %w", err) | ||
| } | ||
| if opts.EnforceConfigMediaType != "" && o.impl.ManifestConfigInfo(parsedManifest).MediaType != opts.EnforceConfigMediaType { | ||
| return nil, fmt.Errorf( | ||
| "wrong config media type %q, requires %q", | ||
| o.impl.ManifestConfigInfo(parsedManifest).MediaType, | ||
| opts.EnforceConfigMediaType, | ||
| ) | ||
| } | ||
| return parsedManifest, nil | ||
| } | ||
|
|
||
| func (o *OCIArtifact) prepareCache(ctx context.Context, opts *PullOptions) (useCache bool) { | ||
| if opts.CachePath == "" { | ||
| return false | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ import ( | |
| "github.com/cri-o/cri-o/internal/log" | ||
| "github.com/cri-o/cri-o/internal/oci" | ||
| "github.com/cri-o/cri-o/internal/storage" | ||
| "github.com/cri-o/cri-o/internal/storage/references" | ||
| "github.com/cri-o/cri-o/pkg/annotations" | ||
| "github.com/cri-o/cri-o/pkg/config" | ||
| "github.com/cri-o/cri-o/utils" | ||
|
|
@@ -107,7 +108,7 @@ type Container interface { | |
| SpecAddMount(rspec.Mount) | ||
|
|
||
| // SpecAddAnnotations adds annotations to the spec. | ||
| SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolume []oci.ContainerVolume, mountPoint, configStopSignal string, imageResult *storage.ImageResult, isSystemd bool, seccompRef, platformRuntimePath string) error | ||
| SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolume []oci.ContainerVolume, mountPoint, configStopSignal string, imageName *references.RegistryImageReference, imageID *storage.StorageImageID, isSystemd bool, seccompRef, platformRuntimePath string) error | ||
|
Collaborator
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. I have spent a lot of effort (and attracted a lot of disagreement and controversy) to explicitly document the weird semantics of these values, by using names like So, naturally, I want all those caveats to be preserved and the comments to be duplicated if this is going to stop referencing the existing data structures. |
||
|
|
||
| // SpecAddDevices adds devices from the server config, and container CRI config | ||
| SpecAddDevices([]device.Device, []device.Device, bool, bool) error | ||
|
|
@@ -172,7 +173,7 @@ func (c *container) SpecAddMount(r rspec.Mount) { | |
| } | ||
|
|
||
| // SpecAddAnnotation adds all annotations to the spec. | ||
| func (c *container) SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolumes []oci.ContainerVolume, mountPoint, configStopSignal string, imageResult *storage.ImageResult, isSystemd bool, seccompRef, platformRuntimePath string) (err error) { | ||
| func (c *container) SpecAddAnnotations(ctx context.Context, sb SandboxIFace, containerVolumes []oci.ContainerVolume, mountPoint, configStopSignal string, imageName *references.RegistryImageReference, imageID *storage.StorageImageID, isSystemd bool, seccompRef, platformRuntimePath string) (err error) { | ||
|
Collaborator
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. If |
||
| ctx, span := log.StartSpan(ctx) | ||
| defer span.End() | ||
| // Copied from k8s.io/kubernetes/pkg/kubelet/kuberuntime/labels.go | ||
|
|
@@ -223,11 +224,11 @@ func (c *container) SpecAddAnnotations(ctx context.Context, sb SandboxIFace, con | |
|
|
||
| c.spec.AddAnnotation(annotations.UserRequestedImage, userRequestedImage) | ||
| someNameOfThisImage := "" | ||
| if imageResult.SomeNameOfThisImage != nil { | ||
| someNameOfThisImage = imageResult.SomeNameOfThisImage.StringForOutOfProcessConsumptionOnly() | ||
| if imageName != nil { | ||
| someNameOfThisImage = imageName.StringForOutOfProcessConsumptionOnly() | ||
| } | ||
| c.spec.AddAnnotation(annotations.SomeNameOfTheImage, someNameOfThisImage) | ||
| c.spec.AddAnnotation(annotations.ImageRef, imageResult.ID.IDStringForOutOfProcessConsumptionOnly()) | ||
| c.spec.AddAnnotation(annotations.ImageRef, imageID.IDStringForOutOfProcessConsumptionOnly()) | ||
| c.spec.AddAnnotation(annotations.Name, c.Name()) | ||
| c.spec.AddAnnotation(annotations.ContainerID, c.ID()) | ||
| c.spec.AddAnnotation(annotations.SandboxID, sb.ID()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||
| "github.com/sirupsen/logrus" | ||
|
|
||
| "github.com/cri-o/cri-o/internal/config/ociartifact" | ||
|
Collaborator
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.
Is everyone happy with the package names and dependencies here? (That’s entirely up to CRI-O maintainers, not me.) |
||
| "github.com/cri-o/cri-o/internal/log" | ||
| ) | ||
|
|
||
|
|
@@ -76,7 +77,7 @@ | |
| // CreateContainer creates a container with the specified ID. | ||
| // Pointer arguments can be nil. | ||
| // All other arguments are required. | ||
| CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool) (ContainerInfo, error) | ||
| CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged, isRuntimePullImage bool) (ContainerInfo, error) | ||
|
Collaborator
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. Reading the function declaration, I don’t understand what |
||
| // DeleteContainer deletes a container, unmounting it first if need be. | ||
| DeleteContainer(ctx context.Context, idOrName string) error | ||
|
|
||
|
|
@@ -157,7 +158,7 @@ | |
| privileged bool // Applicable to both PodSandboxes and Containers | ||
| } | ||
|
|
||
| func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.SystemContext, containerID string, template *runtimeContainerMetadataTemplate, idMappingsOptions *storage.IDMappingOptions, labelOptions []string) (ci ContainerInfo, retErr error) { | ||
| func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.SystemContext, containerID string, template *runtimeContainerMetadataTemplate, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, isRuntimePullImage bool) (ci ContainerInfo, retErr error) { | ||
| if template.podName == "" || template.podID == "" { | ||
| return ContainerInfo{}, ErrInvalidPodName | ||
| } | ||
|
|
@@ -187,21 +188,31 @@ | |
|
|
||
| // Pull out a copy of the image's configuration. | ||
| // Ideally we would call imageID.imageRef(r.storageImageServer), but storageImageServer does not have access to private data. | ||
| ref, err := istorage.Transport.NewStoreReference(r.storageImageServer.GetStore(), nil, template.imageID.privateID) | ||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| image, err := ref.NewImage(r.ctx, systemContext) | ||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| defer image.Close() | ||
| var imageConfig *v1.Image | ||
| if isRuntimePullImage { | ||
|
Collaborator
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. I don’t know why this parameter needs to exist, and I’m generally suspicious of call trees where a function deep down behaves significantly differently based on an option (and I do consider reading local files vs. reaching out to a network significant); can’t the code be refactored to
Callers could then pass the |
||
| var err error | ||
| artifact := ociartifact.New() | ||
|
|
||
| imageConfig, err := image.OCIConfig(r.ctx) | ||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| imageConfig, err = artifact.GetConfig(r.ctx, metadata.ImageName, &ociartifact.PullOptions{}) | ||
|
Collaborator
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.
I don’t understand the full context but this strongly suggests to me a high risk of a significant structural problem.
Collaborator
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. Where do credentials for registry access come from? Can this work without them, only relying on the node-global cluster pull secret ??! |
||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| } else { | ||
| ref, err := istorage.Transport.NewStoreReference(r.storageImageServer.GetStore(), nil, template.imageID.privateID) | ||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| image, err := ref.NewImage(r.ctx, systemContext) | ||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| defer image.Close() | ||
|
|
||
| imageConfig, err = image.OCIConfig(r.ctx) | ||
| if err != nil { | ||
| return ContainerInfo{}, err | ||
| } | ||
| } | ||
| metadata.Pod = (containerID == metadata.PodID) // Or should this be hard-coded in callers? The caller should know whether it is creating the infra container. | ||
| metadata.CreatedAt = time.Now().Unix() | ||
| mdata, err := json.Marshal(&metadata) | ||
|
|
@@ -222,7 +233,12 @@ | |
| if idMappingsOptions != nil { | ||
| coptions.IDMappingOptions = *idMappingsOptions | ||
| } | ||
| container, err := r.storageImageServer.GetStore().CreateContainer(containerID, names, template.imageID.privateID, "", string(mdata), &coptions) | ||
| imageName := template.imageID.privateID | ||
| if isRuntimePullImage { | ||
| // store.CreateContainer accepts empty image names | ||
| imageName = "" | ||
| } | ||
| container, err := r.storageImageServer.GetStore().CreateContainer(containerID, names, imageName, "", string(mdata), &coptions) | ||
| if err != nil { | ||
| if metadata.Pod { | ||
| logrus.Debugf("Failed to create pod sandbox %s(%s): %v", metadata.PodName, metadata.PodID, err) | ||
|
|
@@ -350,10 +366,10 @@ | |
| namespace: namespace, | ||
| attempt: attempt, | ||
| privileged: privileged, | ||
| }, idMappingsOptions, labelOptions) | ||
| }, idMappingsOptions, labelOptions, false) | ||
| } | ||
|
|
||
| func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool) (ContainerInfo, error) { | ||
| func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, podName, podID, userRequestedImage string, imageID StorageImageID, containerName, containerID, metadataName string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged, isRuntimePullImage bool) (ContainerInfo, error) { | ||
| return r.createContainerOrPodSandbox(systemContext, containerID, &runtimeContainerMetadataTemplate{ | ||
| podName: podName, | ||
| podID: podID, | ||
|
|
@@ -365,7 +381,7 @@ | |
| namespace: "", | ||
| attempt: attempt, | ||
| privileged: privileged, | ||
| }, idMappingsOptions, labelOptions) | ||
| }, idMappingsOptions, labelOptions, isRuntimePullImage) | ||
| } | ||
|
|
||
| func (r *runtimeService) deleteLayerIfMapped(imageID, layerID string) { | ||
|
|
||
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.
(pre-existing?)
srcmust be closed, otherwise this leaves around keep-alive HTTP connections.