mirror of
https://github.com/containers/podman.git
synced 2025-08-01 16:24:58 +08:00
save image remove signatures
remove signatures to podman save since the image formats do not support signatures Close: #7659 Signed-off-by: Qi Wang <qiwan@redhat.com>
This commit is contained in:
@ -177,7 +177,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile
|
|||||||
// SaveImages stores one more images in a multi-image archive.
|
// SaveImages stores one more images in a multi-image archive.
|
||||||
// Note that only `docker-archive` supports storing multiple
|
// Note that only `docker-archive` supports storing multiple
|
||||||
// image.
|
// image.
|
||||||
func (ir *Runtime) SaveImages(ctx context.Context, namesOrIDs []string, format string, outputFile string, quiet bool) (finalErr error) {
|
func (ir *Runtime) SaveImages(ctx context.Context, namesOrIDs []string, format string, outputFile string, quiet, removeSignatures bool) (finalErr error) {
|
||||||
if format != DockerArchive {
|
if format != DockerArchive {
|
||||||
return errors.Errorf("multi-image archives are only supported in in the %q format", DockerArchive)
|
return errors.Errorf("multi-image archives are only supported in in the %q format", DockerArchive)
|
||||||
}
|
}
|
||||||
@ -264,7 +264,7 @@ func (ir *Runtime) SaveImages(ctx context.Context, namesOrIDs []string, format s
|
|||||||
}
|
}
|
||||||
|
|
||||||
img := imageMap[id]
|
img := imageMap[id]
|
||||||
copyOptions := getCopyOptions(sys, writer, nil, nil, SigningOptions{}, "", img.tags)
|
copyOptions := getCopyOptions(sys, writer, nil, nil, SigningOptions{RemoveSignatures: removeSignatures}, "", img.tags)
|
||||||
copyOptions.DestinationCtx.SystemRegistriesConfPath = registries.SystemRegistriesConfPath()
|
copyOptions.DestinationCtx.SystemRegistriesConfPath = registries.SystemRegistriesConfPath()
|
||||||
|
|
||||||
// For copying, we need a source reference that we can create
|
// For copying, we need a source reference that we can create
|
||||||
@ -1584,7 +1584,7 @@ func (i *Image) Comment(ctx context.Context, manifestType string) (string, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save writes a container image to the filesystem
|
// Save writes a container image to the filesystem
|
||||||
func (i *Image) Save(ctx context.Context, source, format, output string, moreTags []string, quiet, compress bool) error {
|
func (i *Image) Save(ctx context.Context, source, format, output string, moreTags []string, quiet, compress, removeSignatures bool) error {
|
||||||
var (
|
var (
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
destRef types.ImageReference
|
destRef types.ImageReference
|
||||||
@ -1636,7 +1636,7 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", "", writer, compress, SigningOptions{}, &DockerRegistryOptions{}, additionaltags); err != nil {
|
if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", "", writer, compress, SigningOptions{RemoveSignatures: removeSignatures}, &DockerRegistryOptions{}, additionaltags); err != nil {
|
||||||
return errors.Wrapf(err, "unable to save %q", source)
|
return errors.Wrapf(err, "unable to save %q", source)
|
||||||
}
|
}
|
||||||
i.newImageEvent(events.Save)
|
i.newImageEvent(events.Save)
|
||||||
|
@ -60,7 +60,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil {
|
if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false, true); err != nil {
|
||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image"))
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -429,7 +429,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false); err != nil {
|
if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false, true); err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format))
|
utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress); err != nil {
|
if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress, true); err != nil {
|
||||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -284,6 +284,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
Format: query.Format,
|
Format: query.Format,
|
||||||
MultiImageArchive: true,
|
MultiImageArchive: true,
|
||||||
Output: output,
|
Output: output,
|
||||||
|
RemoveSignatures: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||||
|
@ -300,6 +300,8 @@ type ImageSaveOptions struct {
|
|||||||
MultiImageArchive bool
|
MultiImageArchive bool
|
||||||
// Output - write image to the specified path.
|
// Output - write image to the specified path.
|
||||||
Output string
|
Output string
|
||||||
|
// Do not save the signature from the source image
|
||||||
|
RemoveSignatures bool
|
||||||
// Quiet - suppress output when copying images
|
// Quiet - suppress output when copying images
|
||||||
Quiet bool
|
Quiet bool
|
||||||
}
|
}
|
||||||
|
@ -482,13 +482,13 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti
|
|||||||
func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error {
|
func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error {
|
||||||
if options.MultiImageArchive {
|
if options.MultiImageArchive {
|
||||||
nameOrIDs := append([]string{nameOrID}, tags...)
|
nameOrIDs := append([]string{nameOrID}, tags...)
|
||||||
return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet)
|
return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet, true)
|
||||||
}
|
}
|
||||||
newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
|
newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress)
|
return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) {
|
func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) {
|
||||||
|
@ -843,7 +843,7 @@ func (i *VarlinkAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.Image
|
|||||||
saveOutput := bytes.NewBuffer([]byte{})
|
saveOutput := bytes.NewBuffer([]byte{})
|
||||||
c := make(chan error)
|
c := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress)
|
err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress, true)
|
||||||
c <- err
|
c <- err
|
||||||
close(c)
|
close(c)
|
||||||
}()
|
}()
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/podman/v2/pkg/rootless"
|
"github.com/containers/podman/v2/pkg/rootless"
|
||||||
. "github.com/containers/podman/v2/test/utils"
|
. "github.com/containers/podman/v2/test/utils"
|
||||||
@ -116,6 +120,71 @@ var _ = Describe("Podman save", func() {
|
|||||||
Expect(save).To(ExitWithError())
|
Expect(save).To(ExitWithError())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman save remove signature", func() {
|
||||||
|
SkipIfRootless("FIXME: Need get in rootless push sign")
|
||||||
|
if podmanTest.Host.Arch == "ppc64le" {
|
||||||
|
Skip("No registry image for ppc64le")
|
||||||
|
}
|
||||||
|
tempGNUPGHOME := filepath.Join(podmanTest.TempDir, "tmpGPG")
|
||||||
|
err := os.Mkdir(tempGNUPGHOME, os.ModePerm)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
origGNUPGHOME := os.Getenv("GNUPGHOME")
|
||||||
|
err = os.Setenv("GNUPGHOME", tempGNUPGHOME)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
defer os.Setenv("GNUPGHOME", origGNUPGHOME)
|
||||||
|
|
||||||
|
port := 5000
|
||||||
|
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", strings.Join([]string{strconv.Itoa(port), strconv.Itoa(port)}, ":"), "docker.io/registry:2.6"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
|
||||||
|
Skip("Cannot start docker registry.")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("gpg", "--import", "sign/secret-key.asc")
|
||||||
|
err = cmd.Run()
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
cmd = exec.Command("cp", "/etc/containers/registries.d/default.yaml", "default.yaml")
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
Skip("no signature store to verify")
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
cmd = exec.Command("cp", "default.yaml", "/etc/containers/registries.d/default.yaml")
|
||||||
|
cmd.Run()
|
||||||
|
}()
|
||||||
|
|
||||||
|
cmd = exec.Command("cp", "sign/key.gpg", "/tmp/key.gpg")
|
||||||
|
Expect(cmd.Run()).To(BeNil())
|
||||||
|
sigstore := `
|
||||||
|
default-docker:
|
||||||
|
sigstore: file:///var/lib/containers/sigstore
|
||||||
|
sigstore-staging: file:///var/lib/containers/sigstore
|
||||||
|
`
|
||||||
|
Expect(ioutil.WriteFile("/etc/containers/registries.d/default.yaml", []byte(sigstore), 0755)).To(BeNil())
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"tag", ALPINE, "localhost:5000/alpine"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"push", "--tls-verify=false", "--sign-by", "foo@bar.com", "localhost:5000/alpine"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"rmi", ALPINE, "localhost:5000/alpine"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"pull", "--tls-verify=false", "--signature-policy=sign/policy.json", "localhost:5000/alpine"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
outfile := filepath.Join(podmanTest.TempDir, "temp.tar")
|
||||||
|
save := podmanTest.Podman([]string{"save", "remove-signatures=true", "-o", outfile, "localhost:5000/alpine"})
|
||||||
|
save.WaitWithDefaultTimeout()
|
||||||
|
Expect(save).To(ExitWithError())
|
||||||
|
})
|
||||||
|
|
||||||
It("podman save image with digest reference", func() {
|
It("podman save image with digest reference", func() {
|
||||||
// pull a digest reference
|
// pull a digest reference
|
||||||
session := podmanTest.PodmanNoCache([]string{"pull", ALPINELISTDIGEST})
|
session := podmanTest.PodmanNoCache([]string{"pull", ALPINELISTDIGEST})
|
||||||
|
30
test/e2e/sign/key.gpg
Normal file
30
test/e2e/sign/key.gpg
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQENBF8kNqwBCAC0x3Kog+WlDNwcR6rWIP8Gj2T6LrQ2/3knSyAWzTgC/OBB6Oh0
|
||||||
|
KAokXLjy8J3diG3EaSltE7erGG/bZCz8jYvMiwDJScON4zzidotqjoY80E+NeRDg
|
||||||
|
CC0gqvqmh0ftJIjYNBHzSxqrGRQwzwZU+u6ezlE8+0dvsHcHY+MRnxXJQrdM07EP
|
||||||
|
Prp85kKckChDlJ1tyGUB/YHieFQmOW5+TERA7ZqQOAQ12Vviv6V4kNfEJJq3MS2c
|
||||||
|
csZpO323tcHt3oebqsZCIElhX7uVw6GAeCw1tm4NZXs4g1yIC21Of/hzPeC18F72
|
||||||
|
splCgKaAOiE9w/nMGLNEYy2NzgEclZLs2Y7jABEBAAG0FGZvb2JhciA8Zm9vQGJh
|
||||||
|
ci5jb20+iQFUBBMBCAA+FiEERyT4ac7LLibByeabqaoHAy6P2bIFAl8kNqwCGwMF
|
||||||
|
CQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQqaoHAy6P2bKtuggAgv54
|
||||||
|
/F8wgi+uMrtFr8rqNtZMDyXRxfXaXUy5uGNfqHD83yqxweEqxiA8lmFkRHixPWtg
|
||||||
|
Z2MniFXMVc9kVmg8GNIIuzewXrPqtXztvuURQo9phK68v8fXEqqT6K25wtq8TiQZ
|
||||||
|
0J3mQIJPPTMe3pCCOyR6+W3iMtQp2AmitxKbzLP3J3GG2i0rG5S147A2rPnzTeMY
|
||||||
|
hds819+JE7jNMD7FkV+TcQlOVl4wyOQhNEJcjb6rA6EUe5+s85pIFTBSyPMJpJ03
|
||||||
|
Y0dLdcSGpKdncGTK2X9+hS96G1+FP/t8hRIDblqUHtBRXe3Ozz6zSqpqu1DbAQSM
|
||||||
|
bIrLYxXfnZEN+ro0dLkBDQRfJDasAQgAncvLLZUHZkJWDPka3ocysJ7+/lmrXyAj
|
||||||
|
T3D4r7UM4oaLBOMKjvaKSDw1uW5qYmTxnnsqFDI0O5+XJxD1/0qEf6l2oUpnILdx
|
||||||
|
Vruf28FuvymbsyhDgs+MBoHz0jLWWPHUW2oWLIqcvaF0BePQ1GS6UoZlmZejsLww
|
||||||
|
cSpbaAHJng7An/iLuqOBr5EdUA5XMXqmdMFDrjh0uZezImJ2Eacu/hshBdu3IY49
|
||||||
|
J5XP18GWrSdUnP27cv3tOii9j5Lfl8QAvCN89vkALIU3eZtnMlWZqLgl5o6COVFm
|
||||||
|
zpyx+iHOoCznQBt0aGoSNmE/dAqWIQS/xCSFqMHI6kNd9N0oR0rEHwARAQABiQE8
|
||||||
|
BBgBCAAmFiEERyT4ac7LLibByeabqaoHAy6P2bIFAl8kNqwCGwwFCQPCZwAACgkQ
|
||||||
|
qaoHAy6P2bJfjQgAje6YR+p1QaNlTN9l4t2kGzy9RhkfYMrTgI2fEqbS9bFJUy3Y
|
||||||
|
3mH+vj/r2gN/kaN8LHH4K1d7fAohBsFqSI0flzHHIx2rfti9zAlbXcAErbnG+f0f
|
||||||
|
k0AaqU7KelU35vjPfNe6Vn7ky6G9CC6jW04NkLZDNFA2GusdYf1aM0LWew5t4WZa
|
||||||
|
quLVFhL36q9eHaogO/fcPR/quvQefHokk+b541ytwMN9l/g43rTbCvAjrUDHwipb
|
||||||
|
Gbw91Wg2XjbecRiCXDKWds2M149BpxUzY5xHFtD5t5WSEE/SkkryGTMmTxS3tuQZ
|
||||||
|
9PdtCPGrNDO6Ts/amORF04Tf+YMJgfv3IWxMeQ==
|
||||||
|
=y0uZ
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
18
test/e2e/sign/policy.json
Normal file
18
test/e2e/sign/policy.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"default": [
|
||||||
|
{
|
||||||
|
"type": "insecureAcceptAnything"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transports": {
|
||||||
|
"docker": {
|
||||||
|
"localhost:5000": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "GPGKeys",
|
||||||
|
"keyPath": "/tmp/key.gpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user