Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions test/extended/registry/signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package registry
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this should belong rather to test/extended/imageapis. But I admit it's ambiguous. Tests in the imageapis directories work with registry as well. And the same goes for tests in images and builds directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i don't think either of these packages is a great location for this test, but I would like to have it in registry as the verification fetching manifest from registry and I want to add additional test in (near) future for the signature endpoint in registry (don't want to have the signature test splitted in two packages)


import (
"fmt"

g "github.com/onsi/ginkgo"
o "github.com/onsi/gomega"

imagesutil "github.com/openshift/origin/test/extended/images"
exutil "github.com/openshift/origin/test/extended/util"

e2e "k8s.io/kubernetes/test/e2e/framework"
)

var _ = g.Describe("[imageapis][registry] image signature workflow", func() {
defer g.GinkgoRecover()

var (
oc = exutil.NewCLI("registry-signing", exutil.KubeConfigPath())
signerBuildFixture = exutil.FixturePath("testdata", "signer-buildconfig.yaml")
)

g.It("can push a signed image to openshift registry and verify it", func() {
g.By("building an signer image that know how to sign images")
_, err := oc.Run("create").Args("-f", signerBuildFixture).Output()
o.Expect(err).NotTo(o.HaveOccurred())
err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), "signer", "latest")
containerLog, _ := oc.Run("logs").Args("builds/signer-1").Output()
e2e.Logf("signer build logs:\n%s\n", containerLog)
o.Expect(err).NotTo(o.HaveOccurred())

g.By("looking up the openshift registry URL")
registryURL, err := imagesutil.GetDockerRegistryURL(oc)
signerImage := fmt.Sprintf("%s/%s/signer:latest", registryURL, oc.Namespace())
signedImage := fmt.Sprintf("%s/%s/signed:latest", registryURL, oc.Namespace())
o.Expect(err).NotTo(o.HaveOccurred())

g.By("obtaining bearer token for the test user")
user := oc.Username()
token, err := oc.Run("whoami").Args("-t").Output()
o.Expect(err).NotTo(o.HaveOccurred())

g.By("granting the image-signer role to test user")
_, err = oc.AsAdmin().Run("adm").Args("policy", "add-cluster-role-to-user", "system:image-signer", oc.Username()).Output()
o.Expect(err).NotTo(o.HaveOccurred())

// TODO: The test user needs to be able to unlink the /dev/random which is owned by a
// root. This cannot be done during image build time because the /dev is plugged into
// container after it starts. This SCC could be avoided in the future when /dev/random
// issue is fixed in Docker.
g.By("granting the anyuid scc to test user")
_, err = oc.AsAdmin().Run("adm").Args("policy", "add-scc-to-user", "anyuid", oc.Username()).Output()
o.Expect(err).NotTo(o.HaveOccurred())

g.By("preparing the image stream where the signed image will be pushed")
_, err = oc.Run("create").Args("imagestream", "signed").Output()
o.Expect(err).NotTo(o.HaveOccurred())

g.By("granting the image-auditor role to test user")
_, err = oc.AsAdmin().Run("adm").Args("policy", "add-cluster-role-to-user", "system:image-auditor", oc.Username()).Output()
o.Expect(err).NotTo(o.HaveOccurred())

pod, err := exutil.NewPodExecutor(oc, "sign-and-push", signerImage)
o.Expect(err).NotTo(o.HaveOccurred())

// Generate GPG key
// Note that we need to replace the /dev/random with /dev/urandom to get more entropy
// into container so we can successfully generate the GPG keypair.
g.By("creating dummy GPG key")
out, err := pod.Exec("rm -f /dev/random; ln -sf /dev/urandom /dev/random && " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😟

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soltysh without this the container will just hang forever as it does not have enough entropy... the same hack is used in the skopeo integration testing...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying you can't do this, I'm just expressing my level of sadness with this hack, sigh...

"GNUPGHOME=/var/lib/origin/gnupg gpg2 --batch --gen-key dummy_key.conf")
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(out).To(o.ContainSubstring("keyring `/var/lib/origin/gnupg/secring.gpg' created"))

// Create kubeconfig for skopeo
g.By("logging as a test user")
out, err = pod.Exec("oc login https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT --token=" + token + " --certificate-authority=/run/secrets/kubernetes.io/serviceaccount/ca.crt")
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(out).To(o.ContainSubstring("Logged in"))

// Sign and copy the origin-pod image into target image stream tag
// TODO: Fix skopeo to pickup the Kubernetes environment variables (remove the $KUBERNETES_MASTER)
g.By("signing the origin-pod:latest image and pushing it into openshift registry")
_, err = pod.Exec("KUBERNETES_MASTER=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT GNUPGHOME=/var/lib/origin/gnupg " +
"skopeo --debug --tls-verify=false copy --sign-by joe@foo.bar --dest-creds " + user + ":" + token + " --dest-tls-verify=false docker://docker.io/openshift/origin-pod:latest atomic:" + signedImage)
o.Expect(err).NotTo(o.HaveOccurred())

err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), "signed", "latest")
o.Expect(err).NotTo(o.HaveOccurred())

g.By("obtaining the signed:latest image name")
imageName, err := oc.Run("get").Args("istag", "signed:latest", "-o", "jsonpath='{.image.metadata.name}'").Output()
o.Expect(err).NotTo(o.HaveOccurred())

g.By("expecting the image to have unverified signature")
out, err = oc.Run("describe").Args("istag", "signed:latest").Output()
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(out).To(o.ContainSubstring("Unverified"))

out, err = pod.Exec("GNUPGHOME=/var/lib/origin/gnupg " +
"oc adm verify-image-signature " + imageName + " --expected-identity=" + signedImage + " --save")
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(out).To(o.ContainSubstring("identity is now confirmed"))

g.By("checking the signature is present on the image and it is now verified")
out, err = oc.Run("describe").Args("istag", "signed:latest").Output()
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(out).To(o.ContainSubstring("Verified"))
})
})
62 changes: 62 additions & 0 deletions test/extended/testdata/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions test/extended/testdata/signer-buildconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
kind: List
apiVersion: v1
items:

- kind: ImageStream
apiVersion: v1
metadata:
name: signer

- kind: BuildConfig
apiVersion: v1
metadata:
name: signer-build
spec:
triggers:
- type: ConfigChange
source:
dockerfile: |
FROM openshift/origin:latest
RUN yum install -y skopeo && yum clean all && mkdir -p gnupg && chmod -R 0777 /var/lib/origin
RUN echo $'%echo Generating openpgp key ...\n\
Key-Type: DSA \n\
Key-Length: 1024 \n\
Subkey-Type: ELG-E \n\
Subkey-Length: 1024 \n\
Name-Real: Joe Tester \n\
Name-Comment: with stupid passphrase \n\
Name-Email: joe@foo.bar \n\
Expire-Date: 0 \n\
Creation-Date: 2017-01-01 \n\
%commit \n\
%echo done \n' >> dummy_key.conf
strategy:
type: Docker
dockerStrategy:
from:
kind: DockerImage
name: openshift/origin:latest
output:
to:
kind: ImageStreamTag
name: signer:latest
31 changes: 31 additions & 0 deletions test/extended/util/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -1439,3 +1439,34 @@ func CheckForBuildEvent(client kcoreclient.CoreV1Interface, build *buildapi.Buil
o.ExpectWithOffset(1, expectedEvent).NotTo(o.BeNil(), "Did not find a %q event on build %s/%s", reason, build.Namespace, build.Name)
o.ExpectWithOffset(1, expectedEvent.Message).To(o.Equal(fmt.Sprintf(message, build.Namespace, build.Name)))
}

type podExecutor struct {
client *CLI
podName string
}

// NewPodExecutor returns an executor capable of running commands in a Pod.
func NewPodExecutor(oc *CLI, name, image string) (*podExecutor, error) {
out, err := oc.Run("run").Args(name, "--labels", "name="+name, "--image", image, "--restart", "Never", "--command", "--", "/bin/bash", "-c", "sleep infinity").Output()
if err != nil {
return nil, fmt.Errorf("error: %v\n(%s)", err, out)
}
_, err = WaitForPods(oc.KubeClient().CoreV1().Pods(oc.Namespace()), ParseLabelsOrDie("name="+name), CheckPodIsReadyFn, 1, 3*time.Minute)
if err != nil {
return nil, err
}
return &podExecutor{client: oc, podName: name}, nil
}

// Exec executes a single command or a bash script in the running pod. It returns the
// command output and error if the command finished with non-zero status code or the
// command took longer then 3 minutes to run.
func (r *podExecutor) Exec(script string) (string, error) {
var out string
waitErr := wait.PollImmediate(1*time.Second, 3*time.Minute, func() (bool, error) {
var err error
out, err = r.client.Run("exec").Args(r.podName, "--", "/bin/bash", "-c", script).Output()
return true, err
})
return out, waitErr
}