Skip to content

Commit

Permalink
Image signature verification worflow test
Browse files Browse the repository at this point in the history
  • Loading branch information
mfojtik committed May 15, 2017
1 parent b38274c commit 1abda05
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 0 deletions.
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

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 && " +
"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
}

0 comments on commit 1abda05

Please sign in to comment.