2021-03-09 08:22:37 +00:00
|
|
|
// Copyright © 2021 Ettore Di Giacinto <mudler@mocaccino.org>
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License along
|
|
|
|
// with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2021-06-01 14:43:31 +00:00
|
|
|
package docker
|
2021-03-09 08:22:37 +00:00
|
|
|
|
|
|
|
import (
|
2021-03-11 16:04:26 +00:00
|
|
|
"context"
|
|
|
|
"encoding/hex"
|
2022-10-18 07:20:20 +00:00
|
|
|
"net/http"
|
2021-03-09 08:22:37 +00:00
|
|
|
"os"
|
2023-02-02 11:48:09 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
|
|
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
2021-03-09 08:22:37 +00:00
|
|
|
|
2021-06-16 21:29:23 +00:00
|
|
|
"github.com/containerd/containerd/images"
|
2021-10-23 22:57:50 +00:00
|
|
|
luetimages "github.com/mudler/luet/pkg/api/core/image"
|
|
|
|
luettypes "github.com/mudler/luet/pkg/api/core/types"
|
|
|
|
|
2021-08-11 08:31:09 +00:00
|
|
|
fileHelper "github.com/mudler/luet/pkg/helpers/file"
|
2021-06-16 21:29:23 +00:00
|
|
|
|
2021-03-11 16:04:26 +00:00
|
|
|
"github.com/docker/cli/cli/trust"
|
|
|
|
"github.com/docker/distribution/reference"
|
2024-03-15 08:26:32 +00:00
|
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
2021-03-11 16:04:26 +00:00
|
|
|
"github.com/docker/docker/registry"
|
2021-06-04 08:12:37 +00:00
|
|
|
"github.com/google/go-containerregistry/pkg/authn"
|
|
|
|
"github.com/google/go-containerregistry/pkg/name"
|
2021-12-26 19:06:15 +00:00
|
|
|
"github.com/google/go-containerregistry/pkg/v1/daemon"
|
2022-10-18 07:20:20 +00:00
|
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
2021-10-24 15:43:33 +00:00
|
|
|
"github.com/mudler/luet/pkg/api/core/bus"
|
2021-03-11 16:04:26 +00:00
|
|
|
"github.com/opencontainers/go-digest"
|
2021-06-04 08:12:37 +00:00
|
|
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
2021-03-09 08:22:37 +00:00
|
|
|
"github.com/pkg/errors"
|
2021-03-11 16:04:26 +00:00
|
|
|
"github.com/theupdateframework/notary/tuf/data"
|
2021-03-09 08:22:37 +00:00
|
|
|
)
|
|
|
|
|
2023-02-02 11:48:09 +00:00
|
|
|
const (
|
|
|
|
filePrefix = "file://"
|
|
|
|
fileImageSeparator = ":/"
|
|
|
|
)
|
|
|
|
|
2021-03-11 16:04:26 +00:00
|
|
|
// See also https://github.com/docker/cli/blob/88c6089300a82d3373892adf6845a4fed1a4ba8d/cli/command/image/trust.go#L171
|
|
|
|
|
2024-03-15 08:26:32 +00:00
|
|
|
func verifyImage(image string, authConfig *registrytypes.AuthConfig) (string, error) {
|
2021-03-11 16:04:26 +00:00
|
|
|
ref, err := reference.ParseAnyReference(image)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrapf(err, "invalid reference %s", image)
|
|
|
|
}
|
|
|
|
|
|
|
|
// only check if image ref doesn't contain hashes
|
|
|
|
if _, ok := ref.(reference.Digested); !ok {
|
|
|
|
namedRef, ok := ref.(reference.Named)
|
|
|
|
if !ok {
|
|
|
|
return "", errors.New("failed to resolve image digest using content trust: reference is not named")
|
|
|
|
}
|
|
|
|
namedRef = reference.TagNameOnly(namedRef)
|
|
|
|
taggedRef, ok := namedRef.(reference.NamedTagged)
|
|
|
|
if !ok {
|
|
|
|
return "", errors.New("failed to resolve image digest using content trust: reference is not tagged")
|
|
|
|
}
|
|
|
|
|
|
|
|
resolvedImage, err := trustedResolveDigest(context.Background(), taggedRef, authConfig, "luet")
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "failed to resolve image digest using content trust")
|
|
|
|
}
|
|
|
|
resolvedFamiliar := reference.FamiliarString(resolvedImage)
|
|
|
|
return resolvedFamiliar, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2024-03-15 08:26:32 +00:00
|
|
|
func trustedResolveDigest(ctx context.Context, ref reference.NamedTagged, authConfig *registrytypes.AuthConfig, useragent string) (reference.Canonical, error) {
|
2021-03-11 16:04:26 +00:00
|
|
|
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
notaryRepo, err := trust.GetNotaryRepository(os.Stdin, os.Stdout, useragent, repoInfo, authConfig, "pull")
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
|
|
|
}
|
|
|
|
|
|
|
|
t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trust.NotaryError(repoInfo.Name.Name(), err)
|
|
|
|
}
|
|
|
|
// Only get the tag if it's in the top level targets role or the releases delegation role
|
|
|
|
// ignore it if it's in any other delegation roles
|
|
|
|
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
|
|
|
return nil, trust.NotaryError(repoInfo.Name.Name(), errors.Errorf("No trust data for %s", reference.FamiliarString(ref)))
|
|
|
|
}
|
|
|
|
|
|
|
|
h, ok := t.Hashes["sha256"]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("no valid hash, expecting sha256")
|
|
|
|
}
|
|
|
|
|
|
|
|
dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h))
|
|
|
|
|
|
|
|
// Allow returning canonical reference with tag
|
|
|
|
return reference.WithDigest(ref, dgst)
|
|
|
|
}
|
|
|
|
|
2021-06-04 08:12:37 +00:00
|
|
|
type staticAuth struct {
|
2024-03-15 08:26:32 +00:00
|
|
|
auth *registrytypes.AuthConfig
|
2021-06-04 08:12:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s staticAuth) Authorization() (*authn.AuthConfig, error) {
|
|
|
|
if s.auth == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return &authn.AuthConfig{
|
|
|
|
Username: s.auth.Username,
|
|
|
|
Password: s.auth.Password,
|
|
|
|
Auth: s.auth.Auth,
|
|
|
|
IdentityToken: s.auth.IdentityToken,
|
|
|
|
RegistryToken: s.auth.RegistryToken,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2021-06-04 10:08:57 +00:00
|
|
|
// UnpackEventData is the data structure to pass for the bus events
|
|
|
|
type UnpackEventData struct {
|
|
|
|
Image string
|
|
|
|
Dest string
|
|
|
|
}
|
|
|
|
|
2021-06-16 21:29:23 +00:00
|
|
|
// DownloadAndExtractDockerImage extracts a container image natively. It supports privileged/unprivileged mode
|
2024-03-15 08:26:32 +00:00
|
|
|
func DownloadAndExtractDockerImage(ctx luettypes.Context, image, dest string, auth *registrytypes.AuthConfig, verify bool) (*images.Image, error) {
|
2021-03-11 16:04:26 +00:00
|
|
|
if verify {
|
|
|
|
img, err := verifyImage(image, auth)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed verifying image")
|
|
|
|
}
|
|
|
|
image = img
|
|
|
|
}
|
|
|
|
|
2021-08-11 08:31:09 +00:00
|
|
|
if !fileHelper.Exists(dest) {
|
|
|
|
if err := os.MkdirAll(dest, os.ModePerm); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "cannot create destination directory")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-04 08:12:37 +00:00
|
|
|
ref, err := name.ParseReference(image)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-10-18 07:20:20 +00:00
|
|
|
img, err := remote.Image(ref, remote.WithAuth(staticAuth{auth}), remote.WithTransport(http.DefaultTransport))
|
2021-06-04 08:12:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := img.Manifest()
|
2021-03-09 08:22:37 +00:00
|
|
|
if err != nil {
|
2021-06-04 08:12:37 +00:00
|
|
|
return nil, err
|
2021-03-09 08:22:37 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 08:12:37 +00:00
|
|
|
mt, err := img.MediaType()
|
2021-03-09 08:22:37 +00:00
|
|
|
if err != nil {
|
2021-06-04 08:12:37 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2021-03-09 08:22:37 +00:00
|
|
|
|
2021-06-04 08:12:37 +00:00
|
|
|
d, err := img.Digest()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-03-09 08:22:37 +00:00
|
|
|
}
|
|
|
|
|
2021-06-16 16:17:08 +00:00
|
|
|
bus.Manager.Publish(bus.EventImagePreUnPack, UnpackEventData{Image: image, Dest: dest})
|
|
|
|
|
2021-10-23 22:57:50 +00:00
|
|
|
var c int64
|
|
|
|
c, _, err = luetimages.ExtractTo(
|
|
|
|
ctx,
|
|
|
|
img,
|
|
|
|
dest,
|
2021-10-24 10:09:08 +00:00
|
|
|
nil,
|
2021-10-23 22:57:50 +00:00
|
|
|
)
|
2021-06-16 21:29:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-06-04 08:12:37 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 10:08:57 +00:00
|
|
|
bus.Manager.Publish(bus.EventImagePostUnPack, UnpackEventData{Image: image, Dest: dest})
|
|
|
|
|
2021-10-14 20:05:30 +00:00
|
|
|
return &images.Image{
|
|
|
|
Name: image,
|
|
|
|
Labels: m.Annotations,
|
|
|
|
Target: specs.Descriptor{
|
|
|
|
MediaType: string(mt),
|
|
|
|
Digest: digest.Digest(d.String()),
|
|
|
|
Size: c,
|
2021-06-04 08:12:37 +00:00
|
|
|
},
|
|
|
|
}, nil
|
2021-03-09 08:22:37 +00:00
|
|
|
}
|
2021-12-26 19:06:15 +00:00
|
|
|
|
2022-10-18 07:20:20 +00:00
|
|
|
func ExtractDockerImage(ctx luettypes.Context, local, dest string) (*images.Image, error) {
|
2023-02-02 11:48:09 +00:00
|
|
|
var img v1.Image
|
2021-12-26 19:06:15 +00:00
|
|
|
if !fileHelper.Exists(dest) {
|
|
|
|
if err := os.MkdirAll(dest, os.ModePerm); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "cannot create destination directory")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-02 11:48:09 +00:00
|
|
|
var err error
|
|
|
|
if strings.HasPrefix(local, filePrefix) {
|
|
|
|
parts := strings.Split(local, fileImageSeparator)
|
|
|
|
if len(parts) == 2 && parts[1] != "" {
|
|
|
|
img, err = tarball.ImageFromPath(parts[1], nil)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ref, err := name.ParseReference(local)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
img, err = daemon.Image(ref)
|
2021-12-26 19:06:15 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := img.Manifest()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mt, err := img.MediaType()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
d, err := img.Digest()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var c int64
|
|
|
|
c, _, err = luetimages.ExtractTo(
|
|
|
|
ctx,
|
|
|
|
img,
|
|
|
|
dest,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
bus.Manager.Publish(bus.EventImagePostUnPack, UnpackEventData{Image: local, Dest: dest})
|
|
|
|
|
|
|
|
return &images.Image{
|
|
|
|
Name: local,
|
|
|
|
Labels: m.Annotations,
|
|
|
|
Target: specs.Descriptor{
|
|
|
|
MediaType: string(mt),
|
|
|
|
Digest: digest.Digest(d.String()),
|
|
|
|
Size: c,
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|