From d462da676cf0e97420d42ea64d72f69cab675922 Mon Sep 17 00:00:00 2001
From: Miloslav Trmač <mitr@redhat.com>
Date: Fri, 29 Jul 2022 00:08:40 +0200
Subject: Add support for creating sigstore signatures, and providing
 passphrases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Allow creating sigstore signatures via --sign-by-sigstore-private-key .
  Like existing --sign-by, it does not work remote (in this case
  because we would have to copy the private key to the server).
- Allow passing a passphrase (which is mandatory for sigstore private keys)
  via --sign-passphrase-file; if it is not provided, prompt interactively.
- Also, use that passphrase for --sign-by as well, allowing non-interactive
  GPG use. (But --sign-passphrase-file can only be used with _one of_
  --sign-by and --sign-by-sigstore-private-key.)

Note that unlike the existing code, (podman build) does not yet
implement sigstore (I'm not sure why it needs to, it seems not to
push images?) because Buildah does not expose the feature yet.

Also, (podman image sign) was not extended to support sigstore.

The test for this follows existing (podman image sign) tests
and doesn't work rootless; that could be improved by exposing
a registries.d override option.

The test for push is getting large; I didn't want to
start yet another registry container, but that would be an
alternative.  In the future, Ginkgo's Ordered/BeforeAll
would allow starting a registry once and using it for two
tests.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
---
 test/e2e/push_test.go                              | 40 ++++++++++++++++++++++
 test/e2e/sign/policy.json                          |  6 ++++
 test/e2e/testdata/sigstore-key.key                 | 11 ++++++
 test/e2e/testdata/sigstore-key.key.pass            |  1 +
 test/e2e/testdata/sigstore-key.pub                 |  4 +++
 .../testdata/sigstore-registries.d-fragment.yaml   |  3 ++
 6 files changed, 65 insertions(+)
 create mode 100644 test/e2e/testdata/sigstore-key.key
 create mode 100644 test/e2e/testdata/sigstore-key.key.pass
 create mode 100644 test/e2e/testdata/sigstore-key.pub
 create mode 100644 test/e2e/testdata/sigstore-registries.d-fragment.yaml

(limited to 'test')

diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go
index 0faa040f4..898d21d00 100644
--- a/test/e2e/push_test.go
+++ b/test/e2e/push_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
 
@@ -136,6 +137,45 @@ var _ = Describe("Podman push", func() {
 			Expect(fi.Name()).To(Equal("digestfile.txt"))
 			Expect(push2).Should(Exit(0))
 		}
+
+		if !IsRemote() { // Remote does not support signing
+			By("pushing and pulling with sigstore signatures")
+			// Ideally, this should set SystemContext.RegistriesDirPath, but Podman currently doesn’t
+			// expose that as an option. So, for now, modify /etc/directly, and skip testing sigstore if
+			// we don’t 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 {
+				fmt.Fprintf(os.Stderr, "Skipping sigstore tests because /etc/containers/registries.d isn’t writable: %s", string(output))
+			} else {
+				defer func() {
+					err := os.Remove(systemRegistriesDAddition)
+					Expect(err).ToNot(HaveOccurred())
+				}()
+
+				// 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(Exit(0))
+				Expect(len(push.ErrorToString())).To(Equal(0))
+
+				pull := podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", "sign/policy.json", "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(Exit(0))
+				Expect(len(push.ErrorToString())).To(Equal(0))
+
+				pull = podmanTest.Podman([]string{"pull", "-q", "--tls-verify=false", "--signature-policy", "sign/policy.json", "localhost:5000/sigstore-signed"})
+				pull.WaitWithDefaultTimeout()
+				Expect(pull).Should(Exit(0))
+			}
+		}
 	})
 
 	It("podman push to local registry with authorization", func() {
diff --git a/test/e2e/sign/policy.json b/test/e2e/sign/policy.json
index ab01137bf..812c14989 100644
--- a/test/e2e/sign/policy.json
+++ b/test/e2e/sign/policy.json
@@ -12,6 +12,12 @@
                     "keyType": "GPGKeys",
                     "keyPath": "/tmp/key.gpg"
                 }
+            ],
+            "localhost:5000/sigstore-signed": [
+                {
+                    "type": "sigstoreSigned",
+                    "keyPath": "testdata/sigstore-key.pub"
+                }
             ]
         }
     }
diff --git a/test/e2e/testdata/sigstore-key.key b/test/e2e/testdata/sigstore-key.key
new file mode 100644
index 000000000..c4eed76a8
--- /dev/null
+++ b/test/e2e/testdata/sigstore-key.key
@@ -0,0 +1,11 @@
+-----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
+eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
+OCwicCI6MX0sInNhbHQiOiI2ckxVcEl1M1pTallrY3dua1pNVktuTHNDUjRENTJv
+Y3J5Wmh2anZ4L1VrPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
+Iiwibm9uY2UiOiJMTVpkeTNBL285NS9SektUZGR3RURhODJTVThVcDdlKyJ9LCJj
+aXBoZXJ0ZXh0IjoiNkkzUlRCc1IwRXpHZWs0SE5LazlVdlpyMEp6Y1Bxemw0ZkEr
+SitJdHlCc0RBSkcyNmhESnFLUDFuQkJTUE5XdHpJRzJUVzQ5Z2hObEJmQy9qYVNk
+eFo2QmhXYk9ldlY0MDB4WjVNZ1oyVHdGSnJxaE9HK0JMdmNvanVkc2tOUFpJTlpE
+LytFZVBIYTRlRVJPTWhnSWlTRC9BYTd3eitlc2trVjkrN216Y3N2RVRiTTJTZGd6
+L3daMUtqV3FlOUc2MWlXSTJPSm1rRlhxQWc9PSJ9
+-----END ENCRYPTED COSIGN PRIVATE KEY-----
diff --git a/test/e2e/testdata/sigstore-key.key.pass b/test/e2e/testdata/sigstore-key.key.pass
new file mode 100644
index 000000000..beb5c7687
--- /dev/null
+++ b/test/e2e/testdata/sigstore-key.key.pass
@@ -0,0 +1 @@
+sigstore pass
diff --git a/test/e2e/testdata/sigstore-key.pub b/test/e2e/testdata/sigstore-key.pub
new file mode 100644
index 000000000..1f470f72b
--- /dev/null
+++ b/test/e2e/testdata/sigstore-key.pub
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX/AWbBiFPuAU5+ys+Ce8YFPhTr1a
+nM7A8h6NrQi6w8w8/4dJCzlGH4SN+P93nopATs6jDXs4Lpc2/tiA1SBmzA==
+-----END PUBLIC KEY-----
diff --git a/test/e2e/testdata/sigstore-registries.d-fragment.yaml b/test/e2e/testdata/sigstore-registries.d-fragment.yaml
new file mode 100644
index 000000000..d79f4c935
--- /dev/null
+++ b/test/e2e/testdata/sigstore-registries.d-fragment.yaml
@@ -0,0 +1,3 @@
+docker:
+  localhost:5000/sigstore-signed:
+    use-sigstore-attachments: true
-- 
cgit v1.2.3-54-g00ecf