podman/test/e2e/push_test.go

438 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package integration
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
. "github.com/containers/podman/v5/test/utils"
"github.com/containers/storage/pkg/archive"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman push", func() {
BeforeEach(func() {
podmanTest.AddImageToRWStore(ALPINE)
})
It("podman push to containers/storage", func() {
SkipIfRemote("Remote push does not support containers-storage transport")
session := podmanTest.Podman([]string{"push", "-q", ALPINE, "containers-storage:busybox:test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.Podman([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman push to dir", func() {
SkipIfRemote("Remote push does not support dir transport")
bbdir := filepath.Join(podmanTest.TempDir, "busybox")
session := podmanTest.Podman([]string{"push", "-q", "--remove-signatures", ALPINE,
fmt.Sprintf("dir:%s", bbdir)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
bbdir = filepath.Join(podmanTest.TempDir, "busybox")
session = podmanTest.Podman([]string{"push", "-q", "--format", "oci", ALPINE,
fmt.Sprintf("dir:%s", bbdir)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman push to oci with compression-format and compression-level", func() {
SkipIfRemote("Remote push does not support dir transport")
bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci")
// Invalid compression format specified, it must fail
session := podmanTest.Podman([]string{"push", "-q", "--compression-format=gzip", "--compression-level=40", ALPINE, fmt.Sprintf("oci:%s", bbdir)})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(125))
output := session.ErrorToString()
Expect(output).To(ContainSubstring("invalid compression level"))
session = podmanTest.Podman([]string{"push", "-q", "--compression-format=zstd", "--remove-signatures", ALPINE,
fmt.Sprintf("oci:%s", bbdir)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
foundZstdFile := false
blobsDir := filepath.Join(bbdir, "blobs/sha256")
blobs, err := os.ReadDir(blobsDir)
Expect(err).ToNot(HaveOccurred())
for _, f := range blobs {
blobPath := filepath.Join(blobsDir, f.Name())
sourceFile, err := os.ReadFile(blobPath)
Expect(err).ToNot(HaveOccurred())
compressionType := archive.DetectCompression(sourceFile)
if compressionType == archive.Zstd {
foundZstdFile = true
break
}
}
Expect(foundZstdFile).To(BeTrue(), "found zstd file")
})
It("push test --force-compression", func() {
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
if isRootless() {
err := podmanTest.RestoreArtifact(REGISTRY_IMAGE)
Expect(err).ToNot(HaveOccurred())
}
lock := GetPortLock("5000")
defer lock.Unlock()
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
Skip("Cannot start docker registry.")
}
session = podmanTest.Podman([]string{"build", "-t", "imageone", "build/basicalpine"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", "imageone", "localhost:5000/image"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
skopeoInspect := []string{"inspect", "--tls-verify=false", "--raw", "docker://localhost:5000/image:latest"}
skopeo := SystemExec("skopeo", skopeoInspect)
skopeo.WaitWithDefaultTimeout()
Expect(skopeo).Should(ExitCleanly())
output := skopeo.OutputToString()
// Default compression is gzip and push with `--force-compression=false` no traces of `zstd` should be there.
Expect(output).ToNot(ContainSubstring("zstd"))
push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--force-compression=false", "--compression-format", "zstd", "--remove-signatures", "imageone", "localhost:5000/image"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
skopeo = SystemExec("skopeo", skopeoInspect)
skopeo.WaitWithDefaultTimeout()
Expect(skopeo).Should(ExitCleanly())
output = skopeo.OutputToString()
// Although `--compression-format` is `zstd` but still no traces of `zstd` should be in image
// since blobs must be reused from last `gzip` image.
Expect(output).ToNot(ContainSubstring("zstd"))
push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--compression-format", "zstd", "--force-compression", "--remove-signatures", "imageone", "localhost:5000/image"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
skopeo = SystemExec("skopeo", skopeoInspect)
skopeo.WaitWithDefaultTimeout()
Expect(skopeo).Should(ExitCleanly())
output = skopeo.OutputToString()
// Should contain `zstd` layer, substring `zstd` is enough to confirm in skopeo inspect output that `zstd` layer is present.
Expect(output).To(ContainSubstring("zstd"))
})
It("podman push to local registry", func() {
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
if isRootless() {
err := podmanTest.RestoreArtifact(REGISTRY_IMAGE)
Expect(err).ToNot(HaveOccurred())
}
lock := GetPortLock("5000")
defer lock.Unlock()
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
Skip("Cannot start docker registry.")
}
push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
push = podmanTest.Podman([]string{"push", "--compression-format=gzip", "--compression-level=1", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))
output := push.ErrorToString()
Expect(output).To(ContainSubstring("Copying blob "))
Expect(output).To(ContainSubstring("Copying config "))
Expect(output).To(ContainSubstring("Writing manifest to image destination"))
bitSize := 1024
keyFileName := filepath.Join(podmanTest.TempDir, "key")
publicKeyFileName, _, err := WriteRSAKeyPair(keyFileName, bitSize)
Expect(err).ToNot(HaveOccurred())
if !IsRemote() { // Remote does not support --encryption-key
push = podmanTest.Podman([]string{"push", "-q", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
}
// Test --digestfile option
digestFile := filepath.Join(podmanTest.TempDir, "digestfile.txt")
push2 := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--digestfile=" + digestFile, "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
push2.WaitWithDefaultTimeout()
fi, err := os.Lstat(digestFile)
Expect(err).ToNot(HaveOccurred())
Expect(fi.Name()).To(Equal("digestfile.txt"))
Expect(push2).Should(ExitCleanly())
if !IsRemote() { // Remote does not support signing
By("pushing and pulling with --sign-by-sigstore-private-key")
// Ideally, this should set SystemContext.RegistriesDirPath, but Podman currently doesnt
// expose that as an option. So, for now, modify /etc/directly, and skip testing sigstore if
// we dont have permission to do so.
systemRegistriesDAddition := "/etc/containers/registries.d/podman-test-only-temporary-addition.yaml"
cmd := exec.Command("cp", "testdata/sigstore-registries.d-fragment.yaml", systemRegistriesDAddition)
output, err := cmd.CombinedOutput()
if err != nil {
GinkgoWriter.Printf("Skipping sigstore tests because /etc/containers/registries.d isnt writable: %s\n", string(output))
} else {
defer func() {
err := os.Remove(systemRegistriesDAddition)
Expect(err).ToNot(HaveOccurred())
}()
// Generate a signature verification policy file
policyPath := generatePolicyFile(podmanTest.TempDir)
defer os.Remove(policyPath)
// Verify that the policy rejects unsigned images
push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/sigstore-signed"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
pull := podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5000/sigstore-signed"})
pull.WaitWithDefaultTimeout()
Expect(pull).To(ExitWithError())
Expect(pull.ErrorToString()).To(ContainSubstring("A signature was required, but no signature exists"))
// Sign an image, and verify it is accepted.
push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", "--sign-by-sigstore-private-key", "testdata/sigstore-key.key", "--sign-passphrase-file", "testdata/sigstore-key.key.pass", ALPINE, "localhost:5000/sigstore-signed"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5000/sigstore-signed"})
pull.WaitWithDefaultTimeout()
Expect(pull).Should(ExitCleanly())
By("pushing and pulling with --sign-by-sigstore")
// Verify that the policy rejects unsigned images
push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/sigstore-signed-params"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5000/sigstore-signed-params"})
pull.WaitWithDefaultTimeout()
Expect(pull).To(ExitWithError())
Expect(pull.ErrorToString()).To(ContainSubstring("A signature was required, but no signature exists"))
// Sign an image, and verify it is accepted.
push = podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", "--sign-by-sigstore", "testdata/sigstore-signing-params.yaml", ALPINE, "localhost:5000/sigstore-signed-params"})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", policyPath, "localhost:5000/sigstore-signed-params"})
pull.WaitWithDefaultTimeout()
Expect(pull).Should(ExitCleanly())
}
}
})
It("podman push from local storage with nothing-allowed signature policy", func() {
SkipIfRemote("Remote push does not support dir transport")
denyAllPolicy := filepath.Join(INTEGRATION_ROOT, "test/deny.json")
inspect := podmanTest.Podman([]string{"inspect", "--format={{.ID}}", ALPINE})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(ExitCleanly())
imageID := inspect.OutputToString()
// FIXME FIXME
push := podmanTest.Podman([]string{"push", "--signature-policy", denyAllPolicy, "-q", imageID, "dir:" + filepath.Join(podmanTest.TempDir, imageID)})
push.WaitWithDefaultTimeout()
Expect(push).Should(ExitCleanly())
})
It("podman push to local registry with authorization", func() {
SkipIfRootless("/etc/containers/certs.d not writable")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
authPath := filepath.Join(podmanTest.TempDir, "auth")
err = os.Mkdir(authPath, os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.MkdirAll("/etc/containers/certs.d/localhost:5000", os.ModePerm)
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll("/etc/containers/certs.d/localhost:5000")
cwd, _ := os.Getwd()
certPath := filepath.Join(cwd, "../", "certs")
lock := GetPortLock("5000")
defer lock.Unlock()
htpasswd := SystemExec("htpasswd", []string{"-Bbn", "podmantest", "test"})
htpasswd.WaitWithDefaultTimeout()
Expect(htpasswd).Should(ExitCleanly())
f, err := os.Create(filepath.Join(authPath, "htpasswd"))
Expect(err).ToNot(HaveOccurred())
defer f.Close()
_, err = f.WriteString(htpasswd.OutputToString())
Expect(err).ToNot(HaveOccurred())
err = f.Sync()
Expect(err).ToNot(HaveOccurred())
session := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry", "-v",
strings.Join([]string{authPath, "/auth", "z"}, ":"), "-e", "REGISTRY_AUTH=htpasswd", "-e",
"REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm", "-e", "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd",
"-v", strings.Join([]string{certPath, "/certs", "z"}, ":"), "-e", "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt",
"-e", "REGISTRY_HTTP_TLS_KEY=/certs/domain.key", REGISTRY_IMAGE})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(WaitContainerReady(podmanTest, "registry", "listening on", 20, 1)).To(BeTrue(), "registry container ready")
push := podmanTest.Podman([]string{"push", "--tls-verify=true", "--format=v2s2", "--creds=podmantest:test", ALPINE, "localhost:5000/tlstest"})
push.WaitWithDefaultTimeout()
Expect(push).To(ExitWithError())
Expect(push.ErrorToString()).To(ContainSubstring("x509: certificate signed by unknown authority"))
push = podmanTest.Podman([]string{"push", "--creds=podmantest:test", "--tls-verify=false", ALPINE, "localhost:5000/tlstest"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))
Expect(push.ErrorToString()).To(ContainSubstring("Writing manifest to image destination"))
setup := SystemExec("cp", []string{filepath.Join(certPath, "domain.crt"), "/etc/containers/certs.d/localhost:5000/ca.crt"})
Expect(setup).Should(ExitCleanly())
push = podmanTest.Podman([]string{"push", "--creds=podmantest:wrongpasswd", ALPINE, "localhost:5000/credstest"})
push.WaitWithDefaultTimeout()
Expect(push).To(ExitWithError())
Expect(push.ErrorToString()).To(ContainSubstring("/credstest: authentication required"))
if !IsRemote() {
// remote does not support --cert-dir
push = podmanTest.Podman([]string{"push", "--tls-verify=true", "--creds=podmantest:test", "--cert-dir=fakedir", ALPINE, "localhost:5000/certdirtest"})
push.WaitWithDefaultTimeout()
Expect(push).To(ExitWithError())
Expect(push.ErrorToString()).To(ContainSubstring("x509: certificate signed by unknown authority"))
}
push = podmanTest.Podman([]string{"push", "--creds=podmantest:test", ALPINE, "localhost:5000/defaultflags"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))
Expect(push.ErrorToString()).To(ContainSubstring("Writing manifest to image destination"))
// create and push manifest
session = podmanTest.Podman([]string{"manifest", "create", "localhost:5000/manifesttest"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.Podman([]string{"manifest", "push", "--creds=podmantest:test", "--tls-verify=false", "--all", "localhost:5000/manifesttest"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.ErrorToString()).To(ContainSubstring("Writing manifest list to image destination"))
})
It("podman push and encrypt to oci", func() {
SkipIfRemote("Remote push neither supports oci transport, nor encryption")
bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci")
bitSize := 1024
keyFileName := filepath.Join(podmanTest.TempDir, "key")
publicKeyFileName, _, err := WriteRSAKeyPair(keyFileName, bitSize)
Expect(err).ToNot(HaveOccurred())
session := podmanTest.Podman([]string{"push", "-q", "--encryption-key", "jwe:" + publicKeyFileName, ALPINE, fmt.Sprintf("oci:%s", bbdir)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.Podman([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman push to docker-archive", func() {
SkipIfRemote("Remote push does not support docker-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.Podman([]string{"push", "-q", ALPINE,
fmt.Sprintf("docker-archive:%s:latest", tarfn)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman push to docker daemon", func() {
SkipIfRemote("Remote push does not support docker-daemon transport")
SkipIfRootless("rootless user has no permission to use default docker.sock")
setup := SystemExec("bash", []string{"-c", "systemctl status docker 2>&1"})
if setup.LineInOutputContains("Active: inactive") {
setup = SystemExec("systemctl", []string{"start", "docker"})
Expect(setup).Should(ExitCleanly())
defer func() {
stop := SystemExec("systemctl", []string{"stop", "docker"})
Expect(stop).Should(Exit(0))
}()
} else if setup.ExitCode() != 0 {
Skip("Docker is not available")
}
session := podmanTest.Podman([]string{"push", "-q", ALPINE, "docker-daemon:alpine:podmantest"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
check := SystemExec("docker", []string{"images", "--format", "{{.Repository}}:{{.Tag}}"})
Expect(check).Should(ExitCleanly())
Expect(check.OutputToString()).To(ContainSubstring("alpine:podmantest"))
clean := SystemExec("docker", []string{"rmi", "alpine:podmantest"})
Expect(clean).Should(ExitCleanly())
})
It("podman push to oci-archive", func() {
SkipIfRemote("Remote push does not support oci-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.Podman([]string{"push", "-q", ALPINE,
fmt.Sprintf("oci-archive:%s:latest", tarfn)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman push to docker-archive no reference", func() {
SkipIfRemote("Remote push does not support docker-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.Podman([]string{"push", "-q", ALPINE,
fmt.Sprintf("docker-archive:%s", tarfn)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
It("podman push to oci-archive no reference", func() {
SkipIfRemote("Remote push does not support oci-archive transport")
ociarc := filepath.Join(podmanTest.TempDir, "alp-oci")
session := podmanTest.Podman([]string{"push", "-q", ALPINE,
fmt.Sprintf("oci-archive:%s", ociarc)})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
})
})