Container Image Signatures in OpenShift 4

Luis Javier Arizmendi Alonso
14 min readMar 13, 2020

--

Overview

The usage of digital signatures to validate application content is not new, a common example is RPM package signing. Red Hat Enterprise Linux (RHEL) validates signatures of RPM packages by default. Image signing consists of signing an image and storing its signature in a secure location (HTTP server), a policy is then used by container runtimes to determine whether or not unsigned or unsafe images should be used. Checking the digital signature We are validating where a container image came from, checking that the image has not been tampered with, and setting policies to determine which validated images you will allow being used in your systems, in summary, It gives a strong indicator that the image has not been altered or corrupted, as even a slight modification invalidates the signature.

Image signing is an important step in a secure workflow and should be integrated into the CI/CD pipeline and into any other automated process that deploys Containers Images (see an example below).

That shouldn’t be a big problem since Linux container tools like Podman, Skopeo, Buildah and CRI-O container engine have built-in support for detached signatures. OpenShift 4 uses CRI-O container engine (who verifies the image by decrypted using the signer’s public key) and Skopeo can be used to add the signature of the image that you want to push to the image registry, and then upload that signature to the standard HTTP server that is used as a signature Store.

In the example above is the Developer who create the signature of the image but that could also be done by a signer server (ie. a step in the CI/CD pipeline), usually a server with restricted access, in addition to a secure storage platform to store the signatures. In this case, it is the signer server who creates a signature file that includes the image manifest digest, and encrypts the digest with its private key.

How to use Container Image Signatures in OpenShift 4

The process comprehends mainly two steps. The first one is signing the image, which involves the process of generating a signature for an image and detaching the signature from the image. The second step is using the signature and signed container image, making the container engine policy require that container images from a particular registry to be signed in order to be allowed.

Creating Container Image Signature

OpenShift Container Platform does not automate image signing. Signing requires a developer’s (or the responsible of the image publishing) private GPG key, typically stored securely on a server. The signer is the responsible for generating the signature that embeds the image manifest digest. It is also responsible for publishing the signature to the signatures server. The signatures are generated using the OpenPGP standard, so you can use PGP tools for generating the key pair.

On the signer host, if using Skopeo to create the signature of the images, the /etc/containers/registries.d/ folder contains configuration files that specify where signatures are stored after their generation. This is the default configuration:

[larizmen@localhost ~]$ cat /etc/containers/registries.d/default.yaml# This is a default registries.d configuration file.  You may# add to this file or create additional files in registries.d/.## sigstore: indicates a location that is read and write# sigstore-staging: indicates a location that is only for write## sigstore and sigstore-staging take a value of the following:#   sigstore:  {schema}://location## For reading signatures, schema may be http, https, or file.# For writing signatures, schema may only be file.# This is the default signature write location for docker registries.default-docker:#  sigstore: file:///var/lib/containers/sigstoresigstore-staging: file:///var/lib/containers/sigstore# The 'docker' indicator here is the start of the configuration# for docker registries.## docker:##   privateregistry.com:#    sigstore: http://privateregistry.com/sigstore/#    sigstore-staging: /mnt/nfs/privateregistry/sigstore

Signatures are stored by default in the /var/lib/containers/sigstore directory (check the sigstore-staging parameter in the previous config file). That sigstore-staging defines the path used by Skopeo for storing the signatures. I change the default directory. This is the final configuration:

[larizmen@localhost ~]$ cat /etc/containers/registries.d/default.yaml# This is a default registries.d configuration file.  You may# add to this file or create additional files in registries.d/.## sigstore: indicates a location that is read and write# sigstore-staging: indicates a location that is only for write## sigstore and sigstore-staging take a value of the following:#   sigstore:  {schema}://location## For reading signatures, schema may be http, https, or file.# For writing signatures, schema may only be file.# This is the default signature write location for docker registries.default-docker:#  sigstore: file:///var/lib/containers/sigstoresigstore-staging: file:///var/lib/containers/sigstore# The 'docker' indicator here is the start of the configuration# for docker registries.## docker:##   privateregistry.com:#    sigstore: http://privateregistry.com/sigstore/#    sigstore-staging: /mnt/nfs/privateregistry/sigstore

Signing involves using the OpenPGP standard to encrypt the signature claim text file with a private key, creating a binary signature file. So the first step will be to generate the GPG key pair that will be used to generate a detached signature. If using a Linux system, the gpg2 command could be used to generate a key pair (you are asked for a password for this private key during the creation wizard).

[larizmen@localhost ~]$ gpg2 --gen-keygpg (GnuPG) 2.2.18; Copyright (C) 2019 Free Software Foundation, Inc.This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.Note: Use "gpg --full-generate-key" for a full featured key generation dialog.GnuPG needs to construct a user ID to identify your key.Real name: Luis ArizmendiEmail address: none@fake.esYou selected this USER-ID:"Luis Arizmendi <none@fake.es>"Change (N)ame, (E)mail, or (O)kay/(Q)uit? OWe need to generate a lot of random bytes. It is a good idea to performsome other action (type on the keyboard, move the mouse, utilize thedisks) during the prime generation; this gives the random numbergenerator a better chance to gain enough entropy.We need to generate a lot of random bytes. It is a good idea to performsome other action (type on the keyboard, move the mouse, utilize thedisks) during the prime generation; this gives the random numbergenerator a better chance to gain enough entropy.gpg: /home/larizmen/.gnupg/trustdb.gpg: trustdb createdgpg: key 7F925EEB3A4D6FD1 marked as ultimately trustedgpg: directory '/home/larizmen/.gnupg/openpgp-revocs.d' createdgpg: revocation certificate stored as '/home/larizmen/.gnupg/openpgp-revocs.d/09FCA87C92E2C247C94B26537F925EEB3A4D6FD1.rev'public and secret key created and signed.pub   rsa2048 2020-03-12 [SC] [expires: 2022-03-12]B7A09E5BB10532115D35BE82B375B011BF2575A2uid                      Luis Arizmendi <none@fake.es>sub   rsa2048 2020-03-12 [E] [expires: 2022-03-12]

Now you can see that the GPG key has been created and stored correctly:

[larizmen@localhost ~]$ gpg2 --list-keysgpg: checking the trustdbgpg: marginals needed: 3  completes needed: 1  trust model: pgpgpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1ugpg: next trustdb check due at 2022-03-12/home/larizmen/.gnupg/pubring.kbx---------------------------------pub   rsa2048 2020-03-12 [SC] [expires: 2022-03-12]B7A09E5BB10532115D35BE82B375B011BF2575A2uid           [ultimate] Luis Arizmendi <none@fake.es>sub   rsa2048 2020-03-12 [E] [expires: 2022-03-12]

To verify an image, the signature claim is decoded using the signer’s public key. If the image manifest digest hash matches you have a very high level of certainty the image from the signer has been unaltered. Let’s export the public key then (note that we “select” the key using the email attached to it, none@fake.es in this case):

[larizmen@localhost ~]$ gpg — armor — export none@fake.es > signer-key.pub[larizmen@localhost ~]$ cat signer-key.pub— — -BEGIN PGP PUBLIC KEY BLOCK — — -mQENBF5qR/cBCADNleAaRXUGD+YV5CcqTb5fguOZsOSZUkdx6mKvMHU00JNtZLzJ+SipQswfbcZarWLCV+Itf4NWgAQiw/yUdMtkeja18B617G1YvzCIJX1bZ5xjxsvrtV7Sz4u/04xTlLphqy1SrbnZ8lIf+jIuijJM2s7l3al6KzzyJzjFj1iB3zDK1nIbvthuajQNelSZ1XuFEYww+u4npgtb9uri8KV/wG99aBQ4qGFI+I2j8bsPGnOCWC3t7vMjfh5EECgw0OIv0pfKG3yd1peyTCuSJmMg4eePNGq3DbNdofrRa2SuDMftravIH5Uz4hOAfTv2KK87Qi/6vENF0qJ9PtFPLRFVABEBAAG0HUx1aXMgQXJpem1lbmRpIDxub25lQGZha2UuZXM+iQFUBBMBCAA+FiEEt6CeW7EFMhFdNb6Cs3WwEb8ldaIFAl5qR/cCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQs3WwEb8ldaLj3wgAgjbvi1GtBuRzrop46+rm3AACbJVAun9C36Awaul1aAfeAkj+Tle+eSdecol3H9LqD3RYHP5ZkX2madip3bYej94nU2iO4R5Gr+JoH3vHNXJlRz/fcW5r0l7WwZWbDENRYI7pjmHFXSxTERR9a7Vl2AbR0s9f7U8bfN+uWs19/Dap1vyJ4SyRdEtmeIYdSAINrtAUHPF8Ii7USMW0BlhRDk6aUOGnvixrfWCxWC2phrH85T5T088rZeLmH6xV5ix0DdMia1zfVsmsjSH1Y0PI8blvOjUFD9uWF6wiMjPy0uxQLwLcZSDr7S7FCkx1PqZyJo1LOiQTSA0oXu2o8jiYu7kBDQReakf3AQgA2rlHzRbXIal0Sg3PRO0NcA3iwtb8IhGmTJveSREJhdTexGCVirdq6/j83SgkOzC/cFSitiZ0FpCItTdwvixZEr8RhTcn46CIsT3TciCa81d4vvU/3l9dsIakLoVFUD/HlKnJ9d+tdOwj4ml9PJmWUy2XT9L4bqwfik90+WfeozmqS3WqAhCVNGzLC3LhMWGXSfkwz/Y00bfax7Nz4OHpeNh/05aMv0qwmehmBAsSv/kGjtC66OWxp0M0T72PfIZnMlaSIwadurMVlTW7GxW0gGBw5QKnxobIX2dqcVTMQVbJoNJ7SDCjcXX7M8r2xDBdnCO0U+zk57flCr8z6lA+uwARAQABiQE8BBgBCAAmFiEEt6CeW7EFMhFdNb6Cs3WwEb8ldaIFAl5qR/cCGwwFCQPCZwAACgkQs3WwEb8ldaIhaQf/TTiGJdGDWjLjZN2EwVvR9LZYoLC2XC7C/yuV/EWDitVOUxZsPt2R/1ywUme+EPjEXvCd9/c8Kv6ibhX23HJ9cePCVFYFXtrOG1EPABxq/GNFN6CSjwrtZtDvTuv7jx6nwdbccPZwUawo769cJunvyLwinNhVuUEjaSdceM80fH56jCcPGTftAIArOhgNXc2qcmWv8MuVdwvh1vuOaQYsOXCewMqXkYrVFkMPijKq6cKIZt+X/T3fFoF6TGlZYWMjkvqgdxX7FKczxAFAzMlxq0qD8NaZbPeGsQAOZtcASeQptgMJzPQfydZBHFVJLHF4TlFvMrQAEBBmyWVH4vG8OQ===CUuC— — -END PGP PUBLIC KEY BLOCK — — -

Now We are going to select one image to be signed. For this example, We’ll be using the blog-django-py image and I Will host both signed and not signed images in docker.io.

Let’s use the skopeo command to sign the image and store it in the registry and apply a tag of signed to the signed image. The — sign-by option allows you to generate a detached signature and store it on your signature server, I will use the email address that makes reference to the PGP Key that I created in the previous step. Notice the signed tag applied to the signed image.

[larizmen@localhost ~]$ skopeo copy — sign-by none@fake.es docker://docker.io/luisarizmendi/blog-django-py:1.0 docker://docker.io/luisarizmendi/blog-django-py:1.0.signedGetting image source signaturesCopying blob a7c1df2e6880 skipped: already existsCopying blob 654d5d234e17 skipped: already existsCopying blob 07b134823c92 skipped: already existsCopying blob 7840fed0d319 skipped: already existsCopying blob 09cd351968f0 skipped: already existsCopying blob 3ebd232e04e8 skipped: already existsCopying blob 476fda89a5fa skipped: already existsCopying blob be9435504ff2 skipped: already existsCopying blob 10fd4039524f skipped: already existsCopying blob 4f818c110984 skipped: already existsCopying blob 083a6ce5792f skipped: already existsCopying blob 7ecab0579a31 skipped: already existsCopying blob cde51b95af40 [ — — — — — — — — — — — — — — — — — — — ] 0.0b / 0.0bCopying config 927f823d16 [ — — — — — — — — — — — — — — — — — — — ] 0.0b / 19.4KiBWriting manifest to image destinationSigning manifestStoring signatures

Ok, We are done with the image, but What about the signature?

[larizmen@localhost ~]$ tree /home/larizmen/tmp/imagesignature//home/larizmen/tmp/imagesignature/└── luisarizmendi└── blog-django-py@sha256=e6d1995b66bd856a61c78cf28715a762459803a327cf3638b5b1033e4195903f└── signature-12 directories, 1 file

That signature-1 file is not a text file, but contents the image signature:

[larizmen@localhost ~]$ cat /home/larizmen/tmp/imagesignature/luisarizmendi/blog-django-py@sha256\=e6d1995b66bd856a61c78cf28715a762459803a327cf3638b5b1033e4195903f/signature-1���������t��~��E���$1�e�FV+%e�d&’�(YU+e���d�T��)��٩E�E�i�E�yɩJVJ����z���9��ʼnE�U��y)��I9��)Y�y����V�zzř�y�)J�:J����H��&�e����d�)����F�fVf&���)�I&�I��F��)���Ff�&�i�)iI@�fdb�hjh��h��j�j��d�b`�fa�������Ē���d������̼�”�sKJ�RA��J2�� �M.J*.B�1�3�3T���t]bn������������ymm’�1#���”���7�� ƚ�k��++(L�8`”� �Ŷ��%I+2�y��Jۀ��3���#_$��y�d�8�1�Y����������Z^�:��k�omMx�ٺ����R�O�_/I]�c��M�’1��R��.��yӳjZ��n��l���F����=����������3�S�Z\��f�]�g��N=7K䑻N��¥�FZ�qz�L��V>�0S����*[C_~���n�|g�ĸ!]�O��#��\(�Wp,���c���[larizmen@localhost ~]$

What can be checked easily is that the Digest string associated to the image is the same one than the showed in the directory name created by Skopeo, that’s a way to match the signature with the image:

[larizmen@localhost ~]$ skopeo inspect \> docker://quay.io/luisarizmendi/blog-django-py:1.0.signed | grep Digest“Digest”: “sha256:e6d1995b66bd856a61c78cf28715a762459803a327cf3638b5b1033e4195903f”,

Copy the signature to the Web Server:

[larizmen@localhost ~]$ scp -r /home/larizmen/tmp/imagesignature/* root@1.50.20.3:/var/www/html/images/signature-1

…and check that is accessible:

[larizmen@localhost ~]$ curl http://1.50.20.3:8080/images/luisarizmendi/<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 3.2 Final//EN”><html><head><title>Index of /images/luisarizmendi</title></head><body><h1>Index of /images/luisarizmendi</h1><table><tr><th valign=”top”><img src=”/icons/blank.gif” alt=”[ICO]”></th><th><a href=”?C=N;O=D”>Name</a></th><th><a href=”?C=M;O=A”>Last modified</a></th><th><a href=”?C=S;O=A”>Size</a></th><th><a href=”?C=D;O=A”>Description</a></th></tr><tr><th colspan=”5"><hr></th></tr><tr><td valign=”top”><img src=”/icons/back.gif” alt=”[PARENTDIR]”></td><td><a href=”/images/”>Parent Directory</a> </td><td>&nbsp;</td><td align=”right”> — </td><td>&nbsp;</td></tr><tr><td valign=”top”><img src=”/icons/folder.gif” alt=”[DIR]”></td><td><a href=”blog-django-py@sha256=e6d1995b66bd856a61c78cf28715a762459803a327cf3638b5b1033e4195903f/”>blog-django-py@sha25..&gt;</a></td><td align=”right”>2020–03–13 10:34 </td><td align=”right”> — </td><td>&nbsp;</td></tr><tr><th colspan=”5"><hr></th></tr></table></body></html>

Configuring OpenShift 4 to use Image Signatures

In my OpenShift cluster I’m using Red Hat CoreOS (RHCOS) in the worker nodes (the other option for workers is using regular RHEL servers), since this is the way that you can get the automation of the whole Operating System Lyfecycle, including patches and configuration. This is important since I have to make changes in the config files of the nodes, so I need to use the automation provided by OpenShift to make those changes persistent. OpenShift provides the MachineConfig Operator who is responsible for enforcing the configuration in the nodes, so we’ll need to use it for our configuration.

One point to bear in mind is that you can select the registries that will require to check the signature of the image in order to allow the deployment of the container. The idea is to configure your private registry there but, since I don’t have one in my environment, I decided to do it quick-and-dirty and configure docker.io for this example, so the worker nodes will always “ask for the signature” if someone tries to deploy an image from docker.io. This is not a good idea since I won’t have all the signatures for all the images in docker.io as if that would be my own private registry with my own images, which means that my cluster won’t permit to deploy any image from docker.io registry… but this is just a test to show how it works.

Before reviewing the actual configuration, there is one file that we need to upload to all worker nodes: the public key that is associated with the private key used to sign the image (We exported it with the name signer-key.pub) . That public key will be needed in the OpenShift nodes to verify the images signature, so We’ll have to export the public key to the worker nodes. I always point to the worker nodes because there is where the signed images will be deployed, if you deploy in master nodes you will also need to do this configuration there, actually, you can configure different policies for different nodes, them configure node labels and project selectors so pods only run in nodes with a given policy. For example, some nodes may be reserved for development work, with a more relaxed policy, and some nodes may be reserved for production work, with a more strict policy. That file should be also created by the machineConfig Operator.

Let’s show the configuration changes in the worker files:

1) Configuration files under the /etc/containers/registries.d/ folder specify where to download the signature files for each registry so You should create a YAML file under /etc/containers/registries.d that specifies the location of detached signatures for a given registry server. I decided that, for this test, I won’t create a new file and I will just use the default.yaml that is already there. In this file you have to include the location of the signatures, so I will configure the Web Server where we already uploaded the signature generated by Skopeo under the docker.io registry.

default-docker:
sigstore-staging: file:///var/lib/atomic/sigstore

docker:
docker.io:
sigstore: https://1.50.20.3:8080/images

2) The /etc/containers/policy.json configuration file specifies whether container images downloaded from a registry server require valid signatures. Add an entry to /etc/containers/policy.json that specifies the public GPG key that validates signatures of a given registry server. In that file you have to include the path to the public key under the docker.io registry section that must be created:

{
"default": [
{
"type": "insecureAcceptAnything"
}
],
"transports":
{
"docker": {
"docker.io": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "/etc/pki/developers/signer-key.pub"
}
]
},
"docker-daemon":
{
"": [{"type":"insecureAcceptAnything"}]
}
}
}

So summarizing, We need to include three new items in the machineconfig of the workers:

  1. Create a path and include there the public key
  2. Modify the /etc/containers/registries.d/default.yaml
  3. Modify the /etc/containers/policy.json

Let’s then re-configure the machine config. We create a new config including the files that we need but since the file content needs URL encoding we first create locally the files needed:

larizmen@localhost sign]$ ls
default.yaml policy.json signer-key.pub

Then we can apply python to enconde the files:

python3 -c "import sys, urllib.parse; print(urllib.parse.quote(''.join(sys.stdin.readlines())))"

For example:

[larizmen@localhost sign]$ cat default.yaml | python3 -c "import sys, urllib.parse; print(urllib.parse.quote(''.join(sys.stdin.readlines())))"
default-docker%3A%0A%20%20sigstore-staging%3A%20file%3A///var/lib/atomic/sigstore%0A%20%0Adocker%3A%0A%20%20docker.io%3A%0A%20%20%20%20sigstore%3A%20https%3A//1.50.20.3%3A8080/images%0A%0A

Then we can apply that to the new machineconfig:

[root@ocp-installer ~]# cat signaturemc.yaml
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: worker
name: 50-worker-signatures
spec:
config:
ignition:
version: 2.2.0
storage:
files:
- path: /etc/containers/policy.json
mode: 0644
filesystem: root
contents:
source: data:,%7B%0A%20%20%20%20%22default%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22insecureAcceptAnything%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%2C%0A%20%20%20%20%22transports%22%3A%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22docker%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22docker.io%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22type%22%3A%20%22signedBy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22keyType%22%3A%20%22GPGKeys%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22keyPath%22%3A%20%22/etc/pki/developers/signer-key.pub%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22docker-daemon%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%22%3A%20%5B%7B%22type%22%3A%22insecureAcceptAnything%22%7D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%7D%0A
- path: /etc/containers/registries.d/default.yaml
mode: 0644
filesystem: root
contents:
source: data:,default-docker%3A%0A%20%20sigstore-staging%3A%20file%3A///var/lib/atomic/sigstore%0A%20%0Adocker%3A%0A%20%20docker.io%3A%0A%20%20%20%20sigstore%3A%20https%3A//1.50.20.3%3A8080/images%0A%0A
- path: /etc/pki/developers/signer-key.pub
mode: 0644
filesystem: root
contents:
source: data:,-%20-%20-BEGIN%20PGP%20PUBLIC%20KEY%20BLOCK%20-%20-%20-%0AmQENBF5qR/cBCADNleAaRXUGD%2BYV5CcqTb5fguOZsOSZUkdx6mKvMHU00JNtZLzJ%0A%2BSipQswfbcZarWLCV%2BItf4NWgAQiw/yUdMtkeja18B617G1YvzCIJX1bZ5xjxsvr%0AtV7Sz4u/04xTlLphqy1SrbnZ8lIf%2BjIuijJM2s7l3al6KzzyJzjFj1iB3zDK1nIb%0AvthuajQNelSZ1XuFEYww%2Bu4npgtb9uri8KV/wG99aBQ4qGFI%2BI2j8bsPGnOCWC3t%0A7vMjfh5EECgw0OIv0pfKG3yd1peyTCuSJmMg4eePNGq3DbNdofrRa2SuDMftravI%0AH5Uz4hOAfTv2KK87Qi/6vENF0qJ9PtFPLRFVABEBAAG0HUx1aXMgQXJpem1lbmRp%0AIDxub25lQGZha2UuZXM%2BiQFUBBMBCAA%2BFiEEt6CeW7EFMhFdNb6Cs3WwEb8ldaIF%0AAl5qR/cCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQs3WwEb8l%0AdaLj3wgAgjbvi1GtBuRzrop46%2Brm3AACbJVAun9C36Awaul1aAfeAkj%2BTle%2BeSde%0Acol3H9LqD3RYHP5ZkX2madip3bYej94nU2iO4R5Gr%2BJoH3vHNXJlRz/fcW5r0l7W%0AwZWbDENRYI7pjmHFXSxTERR9a7Vl2AbR0s9f7U8bfN%2BuWs19/Dap1vyJ4SyRdEtm%0AeIYdSAINrtAUHPF8Ii7USMW0BlhRDk6aUOGnvixrfWCxWC2phrH85T5T088rZeLm%0AH6xV5ix0DdMia1zfVsmsjSH1Y0PI8blvOjUFD9uWF6wiMjPy0uxQLwLcZSDr7S7F%0ACkx1PqZyJo1LOiQTSA0oXu2o8jiYu7kBDQReakf3AQgA2rlHzRbXIal0Sg3PRO0N%0AcA3iwtb8IhGmTJveSREJhdTexGCVirdq6/j83SgkOzC/cFSitiZ0FpCItTdwvixZ%0AEr8RhTcn46CIsT3TciCa81d4vvU/3l9dsIakLoVFUD/HlKnJ9d%2BtdOwj4ml9PJmW%0AUy2XT9L4bqwfik90%2BWfeozmqS3WqAhCVNGzLC3LhMWGXSfkwz/Y00bfax7Nz4OHp%0AeNh/05aMv0qwmehmBAsSv/kGjtC66OWxp0M0T72PfIZnMlaSIwadurMVlTW7GxW0%0AgGBw5QKnxobIX2dqcVTMQVbJoNJ7SDCjcXX7M8r2xDBdnCO0U%2Bzk57flCr8z6lA%2B%0AuwARAQABiQE8BBgBCAAmFiEEt6CeW7EFMhFdNb6Cs3WwEb8ldaIFAl5qR/cCGwwF%0ACQPCZwAACgkQs3WwEb8ldaIhaQf/TTiGJdGDWjLjZN2EwVvR9LZYoLC2XC7C/yuV%0A/EWDitVOUxZsPt2R/1ywUme%2BEPjEXvCd9/c8Kv6ibhX23HJ9cePCVFYFXtrOG1EP%0AABxq/GNFN6CSjwrtZtDvTuv7jx6nwdbccPZwUawo769cJunvyLwinNhVuUEjaSdc%0AeM80fH56jCcPGTftAIArOhgNXc2qcmWv8MuVdwvh1vuOaQYsOXCewMqXkYrVFkMP%0AijKq6cKIZt%2BX/T3fFoF6TGlZYWMjkvqgdxX7FKczxAFAzMlxq0qD8NaZbPeGsQAO%0AZtcASeQptgMJzPQfydZBHFVJLHF4TlFvMrQAEBBmyWVH4vG8OQ%3D%3D%0A%3DCUuC%0A-%20-%20-END%20PGP%20PUBLIC%20KEY%20BLOCK%20-%20-%20-%0A

Then We create the new machineconfig:

[root@ocp-installer ~]# oc create -f signaturemc.yamlmachineconfig.machineconfiguration.openshift.io/50-worker-signatures created

At this point, the machineConfigOperator does its magic and starts reconfiguring the nodes (you can check that is progressing with “oc get machineconfigpool”):

After some time We can then check how the changes were made in the nodes either jumping into the node with ssh (using the core user and from a host that have the private ssh key configured in the nodes) or with “oc debug node/worker0.ocp.136.243.40.222.xip.io” that starts a container in that node and gives you access to it without the need to use ssh and the “core” user.

Testing the configuration

I will try to create two “apps” from docker.io: a nginx not signed and my blog-django-py image from what I already have the signature in my Web Server.

Let’s start with the NGINX with no signature:

[root@ocp-installer ~]# oc new-app docker.io/nginx
--> Found container image 6678c7c (8 days old) from docker.io for "docker.io/nginx"

* An image stream tag will be created as "nginx:latest" that will track this image
* This image will be deployed in deployment config "nginx"
* Port 80/tcp will be load balanced by service "nginx"
* Other containers can access this service through the hostname "nginx"
* WARNING: Image "docker.io/nginx" runs as the 'root' user which may not be permitted by your cluster administrator

--> Creating resources ...
imagestream.image.openshift.io "nginx" created
deploymentconfig.apps.openshift.io "nginx" created
service "nginx" created
--> Success
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
'oc expose svc/nginx'
Run 'oc status' to view your app.

If We check the status of the PODs we can see how the nginx pod cannot pull the image:

[root@ocp-installer ~]# oc get pod
NAME READY STATUS RESTARTS AGE
nginx-1-8tgj7 0/1 ImagePullBackOff 0 36s
nginx-1-deploy 1/1 Running 0 44s

Taking a look at the events, We can be sure that the reason is that the signature of that image is not found, with the error “Source image reject
ed: A signature was required, but no signature exists
”:

[root@ocp-installer ~]# oc get event
LAST SEEN TYPE REASON OBJECT MESSAGE
<unknown> Normal Scheduled pod/nginx-1-8tgj7 Successfully assigned testsignatures/nginx-1-8tgj7 to worker0.ocp.136.243.40.222.xip.io
29s Normal Pulling pod/nginx-1-8tgj7 Pulling image "docker.io/nginx@sha256:3936fb3946790d711a68c58be93628e43cbca72439079e16d154b5db216b58da"
27s Warning Failed pod/nginx-1-8tgj7 Failed to pull image "docker.io/nginx@sha256:3936fb3946790d711a68c58be93628e43cbca72439079e16d154b5db216b58da": rpc error: code = Unknown desc = Source image reject
ed: A signature was required, but no signature exists

27s Warning Failed pod/nginx-1-8tgj7 Error: ErrImagePull
13s Normal BackOff pod/nginx-1-8tgj7 Back-off pulling image "docker.io/nginx@sha256:3936fb3946790d711a68c58be93628e43cbca72439079e16d154b5db216b58da"
13s Warning Failed pod/nginx-1-8tgj7 Error: ImagePullBackOff
<unknown> Normal Scheduled pod/nginx-1-deploy Successfully assigned testsignatures/nginx-1-deploy to worker0.ocp.136.243.40.222.xip.io
54s Normal Pulled pod/nginx-1-deploy Container image "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:dd06a8ddfda407007ce348b59424b36a1a43c17bf48102219775e902daad7037" already present on machine
54s Normal Created pod/nginx-1-deploy Created container deployment
54s Normal Started pod/nginx-1-deploy Started container deployment
54s Normal SuccessfulCreate replicationcontroller/nginx-1 Created pod: nginx-1-8tgj7
62s Normal DeploymentCreated deploymentconfig/nginx Created new replication controller "nginx-1" for version 1

Now let’s try with our image with the signature:

[root@ocp-installer ~]# oc new-app docker.io/luisarizmendi/blog-django-py:1.0                                                                                                                                                                 
--> Found container image 927f823 (8 months old) from docker.io for "docker.io/luisarizmendi/blog-django-py:1.0"

Python 3.5
----------
Python 3.5 available as container is a base platform for building and running various Python 3.5 applications and frameworks. Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a
simple but effective approach to object-oriented programming. Python's elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on mo
st platforms.

Tags: builder, python, python35, python-35, rh-python35

* An image stream tag will be created as "blog-django-py:1.0" that will track this image
* This image will be deployed in deployment config "blog-django-py"
* Port 8080/tcp will be load balanced by service "blog-django-py"
* Other containers can access this service through the hostname "blog-django-py"

--> Creating resources ...
imagestream.image.openshift.io "blog-django-py" created
deploymentconfig.apps.openshift.io "blog-django-py" created
service "blog-django-py" created
--> Success
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
'oc expose svc/blog-django-py'
Run 'oc status' to view your app.

… and the result, as expected, is that the POD is up&running:

[root@ocp-installer ~]# oc get pod
NAME READY STATUS RESTARTS AGE
blog-django-py-1-5zkzh 1/1 Running 0 8s
blog-django-py-1-deploy 1/1 Running 0 16s
nginx-1-8tgj7 0/1 ImagePullBackOff 0 2m1s
nginx-1-deploy 1/1 Running 0 2m9s

--

--

Luis Javier Arizmendi Alonso
Luis Javier Arizmendi Alonso

Written by Luis Javier Arizmendi Alonso

I was born some time ago, I’m living daily and, probably, I will eventually die

Responses (1)