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"
|
2021-06-16 16:17:08 +00:00
|
|
|
"fmt"
|
2021-03-09 08:22:37 +00:00
|
|
|
"os"
|
2021-04-19 12:37:19 +00:00
|
|
|
"strings"
|
2021-03-09 08:22:37 +00:00
|
|
|
|
2021-06-16 21:29:23 +00:00
|
|
|
"github.com/containerd/containerd/images"
|
2021-08-11 08:31:09 +00:00
|
|
|
fileHelper "github.com/mudler/luet/pkg/helpers/file"
|
2021-06-16 21:29:23 +00:00
|
|
|
|
|
|
|
continerdarchive "github.com/containerd/containerd/archive"
|
2021-03-11 16:04:26 +00:00
|
|
|
"github.com/docker/cli/cli/trust"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
|
|
"github.com/docker/docker/api/types"
|
2021-06-16 16:17:08 +00:00
|
|
|
"github.com/docker/docker/pkg/archive"
|
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-06-16 21:29:23 +00:00
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
|
|
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
2021-06-04 08:12:37 +00:00
|
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
2021-06-04 10:08:57 +00:00
|
|
|
"github.com/mudler/luet/pkg/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
|
|
|
)
|
|
|
|
|
2021-03-11 16:04:26 +00:00
|
|
|
// See also https://github.com/docker/cli/blob/88c6089300a82d3373892adf6845a4fed1a4ba8d/cli/command/image/trust.go#L171
|
|
|
|
|
|
|
|
func verifyImage(image string, authConfig *types.AuthConfig) (string, error) {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func trustedResolveDigest(ctx context.Context, ref reference.NamedTagged, authConfig *types.AuthConfig, useragent string) (reference.Canonical, error) {
|
|
|
|
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 {
|
|
|
|
auth *types.AuthConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
// UnarchiveLayers extract layers with archive.Untar from docker instead of containerd
|
|
|
|
func UnarchiveLayers(temp string, img v1.Image, image, dest string, auth *types.AuthConfig, verify bool) (int64, error) {
|
|
|
|
layers, err := img.Layers()
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("reading layers from '%s' image failed: %v", image, err)
|
|
|
|
}
|
|
|
|
bus.Manager.Publish(bus.EventImagePreUnPack, UnpackEventData{Image: image, Dest: dest})
|
|
|
|
|
|
|
|
var size int64
|
|
|
|
for _, l := range layers {
|
|
|
|
s, err := l.Size()
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("reading layer size from '%s' image failed: %v", image, err)
|
|
|
|
}
|
|
|
|
size += s
|
|
|
|
|
|
|
|
layerReader, err := l.Uncompressed()
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("reading uncompressed layer from '%s' image failed: %v", image, err)
|
|
|
|
}
|
|
|
|
defer layerReader.Close()
|
|
|
|
|
|
|
|
// Unpack the tarfile to the rootfs path.
|
|
|
|
// FROM: https://godoc.org/github.com/moby/moby/pkg/archive#TarOptions
|
|
|
|
if err := archive.Untar(layerReader, dest, &archive.TarOptions{
|
|
|
|
NoLchown: false,
|
|
|
|
ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted'
|
|
|
|
}); err != nil {
|
|
|
|
return 0, fmt.Errorf("extracting '%s' image to directory %s failed: %v", image, dest, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bus.Manager.Publish(bus.EventImagePostUnPack, UnpackEventData{Image: image, Dest: dest})
|
|
|
|
|
|
|
|
return size, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DownloadAndExtractDockerImage extracts a container image natively. It supports privileged/unprivileged mode
|
2021-10-14 20:05:30 +00:00
|
|
|
func DownloadAndExtractDockerImage(temp, image, dest string, auth *types.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
|
|
|
|
}
|
|
|
|
|
|
|
|
img, err := remote.Image(ref, remote.WithAuth(staticAuth{auth}))
|
|
|
|
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 21:29:23 +00:00
|
|
|
reader := mutate.Extract(img)
|
|
|
|
defer reader.Close()
|
2021-06-16 16:17:08 +00:00
|
|
|
defer os.RemoveAll(temp)
|
2021-06-04 08:12:37 +00:00
|
|
|
|
2021-06-16 16:17:08 +00:00
|
|
|
bus.Manager.Publish(bus.EventImagePreUnPack, UnpackEventData{Image: image, Dest: dest})
|
|
|
|
|
2021-06-16 21:29:23 +00:00
|
|
|
c, err := continerdarchive.Apply(context.TODO(), dest, reader)
|
|
|
|
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-04-19 12:37:19 +00:00
|
|
|
|
|
|
|
func StripInvalidStringsFromImage(s string) string {
|
|
|
|
return strings.ReplaceAll(s, "+", "-")
|
|
|
|
}
|