Container image signatures

Red Hat delivers signatures for the images in the Red Hat Container Registries. Those signatures can be automatically verified when being pulled to OKD 4 clusters by using the Machine Config Operator (MCO).

Quay.io serves most of the images that make up OKD, and only the release image is signed. Release images refer to the approved OKD images, offering a degree of protection against supply chain attacks. However, some extensions to OKD, such as logging, monitoring, and service mesh, are shipped as Operators from the Operator Lifecycle Manager (OLM). Those images ship from the Red Hat Ecosystem Catalog Container images registry.

To verify the integrity of those images between Red Hat registries and your infrastructure, enable signature verification.

Enabling signature verification for Red Hat Container Registries

Enabling container signature validation for Red Hat Container Registries requires writing a signature verification policy file specifying the keys to verify images from these registries. For RHEL8 nodes, the registries are already defined in /etc/containers/registries.d by default.

Procedure

  1. Create a Butane config file, 51-worker-rh-registry-trust.bu, containing the necessary configuration for the worker nodes.

    See “Creating machine configs with Butane” for information about Butane.

    1. variant: openshift
    2. version: 4.0
    3. metadata:
    4. name: 51-worker-rh-registry-trust
    5. labels:
    6. machineconfiguration.openshift.io/role: worker
    7. storage:
    8. files:
    9. - path: /etc/containers/policy.json
    10. mode: 0644
    11. overwrite: true
    12. contents:
    13. inline: |
    14. {
    15. "default": [
    16. {
    17. "type": "insecureAcceptAnything"
    18. }
    19. ],
    20. "transports": {
    21. "docker": {
    22. "registry.access.redhat.com": [
    23. {
    24. "type": "signedBy",
    25. "keyType": "GPGKeys",
    26. "keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"
    27. }
    28. ],
    29. "registry.redhat.io": [
    30. {
    31. "type": "signedBy",
    32. "keyType": "GPGKeys",
    33. "keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"
    34. }
    35. ]
    36. },
    37. "docker-daemon": {
    38. "": [
    39. {
    40. "type": "insecureAcceptAnything"
    41. }
    42. ]
    43. }
    44. }
    45. }
  2. Use Butane to generate a machine config YAML file, 51-worker-rh-registry-trust.yaml, containing the file to be written to disk on the worker nodes:

    1. $ butane 51-worker-rh-registry-trust.bu -o 51-worker-rh-registry-trust.yaml
  3. Apply the created machine config:

    1. $ oc apply -f 51-worker-rh-registry-trust.yaml
  4. Check that the worker machine config pool has rolled out with the new machine config:

    1. Check that the new machine config was created:

      1. $ oc get mc

      Sample output

      1. NAME GENERATEDBYCONTROLLER IGNITIONVERSION AGE
      2. 00-master a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      3. 00-worker a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      4. 01-master-container-runtime a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      5. 01-master-kubelet a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      6. 01-worker-container-runtime a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      7. 01-worker-kubelet a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      8. 51-master-rh-registry-trust 3.2.0 13s
      9. 51-worker-rh-registry-trust 3.2.0 53s (1)
      10. 99-master-generated-crio-seccomp-use-default 3.2.0 25m
      11. 99-master-generated-registries a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      12. 99-master-ssh 3.2.0 28m
      13. 99-worker-generated-crio-seccomp-use-default 3.2.0 25m
      14. 99-worker-generated-registries a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 25m
      15. 99-worker-ssh 3.2.0 28m
      16. rendered-master-af1e7ff78da0a9c851bab4be2777773b a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 8s
      17. rendered-master-cd51fd0c47e91812bfef2765c52ec7e6 a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 24m
      18. rendered-worker-2b52f75684fbc711bd1652dd86fd0b82 a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 24m
      19. rendered-worker-be3b3bce4f4aa52a62902304bac9da3c a2178ad522c49ee330b0033bb5cb5ea132060b0a 3.2.0 48s (2)
      1New machine config
      2New rendered machine config
    2. Check that the worker machine config pool is updating with the new machine config:

      1. $ oc get mcp

      Sample output

      1. NAME CONFIG UPDATED UPDATING DEGRADED MACHINECOUNT READYMACHINECOUNT UPDATEDMACHINECOUNT DEGRADEDMACHINECOUNT AGE
      2. master rendered-master-af1e7ff78da0a9c851bab4be2777773b True False False 3 3 3 0 30m
      3. worker rendered-worker-be3b3bce4f4aa52a62902304bac9da3c False True False 3 0 0 0 30m (1)
      1When the UPDATING field is True, the machine config pool is updating with the new machine config. When the field becomes False, the worker machine config pool has rolled out to the new machine config.
  5. If your cluster uses any RHEL7 worker nodes, when the worker machine config pool is updated, create YAML files on those nodes in the /etc/containers/registries.d directory, which specify the location of the detached signatures for a given registry server. The following example works only for images hosted in registry.access.redhat.com and registry.redhat.io.

    1. Start a debug session to each RHEL7 worker node:

      1. $ oc debug node/<node_name>
    2. Change your root directory to /host:

      1. sh-4.2# chroot /host
    3. Create a /etc/containers/registries.d/registry.redhat.io.yaml file that contains the following:

      1. docker:
      2. registry.redhat.io:
      3. sigstore: https://registry.redhat.io/containers/sigstore
    4. Create a /etc/containers/registries.d/registry.access.redhat.com.yaml file that contains the following:

      1. docker:
      2. registry.access.redhat.com:
      3. sigstore: https://access.redhat.com/webassets/docker/content/sigstore
    5. Exit the debug session.

Verifying the signature verification configuration

After you apply the machine configs to the cluster, the Machine Config Controller detects the new MachineConfig object and generates a new rendered-worker-<hash> version.

Prerequisites

  • You enabled signature verification by using a machine config file.

Procedure

  1. On the command line, run the following command to display information about a desired worker:

    1. $ oc describe machineconfigpool/worker

    Example output of initial worker monitoring

    1. Name: worker
    2. Namespace:
    3. Labels: machineconfiguration.openshift.io/mco-built-in=
    4. Annotations: <none>
    5. API Version: machineconfiguration.openshift.io/v1
    6. Kind: MachineConfigPool
    7. Metadata:
    8. Creation Timestamp: 2019-12-19T02:02:12Z
    9. Generation: 3
    10. Resource Version: 16229
    11. Self Link: /apis/machineconfiguration.openshift.io/v1/machineconfigpools/worker
    12. UID: 92697796-2203-11ea-b48c-fa163e3940e5
    13. Spec:
    14. Configuration:
    15. Name: rendered-worker-f6819366eb455a401c42f8d96ab25c02
    16. Source:
    17. API Version: machineconfiguration.openshift.io/v1
    18. Kind: MachineConfig
    19. Name: 00-worker
    20. API Version: machineconfiguration.openshift.io/v1
    21. Kind: MachineConfig
    22. Name: 01-worker-container-runtime
    23. API Version: machineconfiguration.openshift.io/v1
    24. Kind: MachineConfig
    25. Name: 01-worker-kubelet
    26. API Version: machineconfiguration.openshift.io/v1
    27. Kind: MachineConfig
    28. Name: 51-worker-rh-registry-trust
    29. API Version: machineconfiguration.openshift.io/v1
    30. Kind: MachineConfig
    31. Name: 99-worker-92697796-2203-11ea-b48c-fa163e3940e5-registries
    32. API Version: machineconfiguration.openshift.io/v1
    33. Kind: MachineConfig
    34. Name: 99-worker-ssh
    35. Machine Config Selector:
    36. Match Labels:
    37. machineconfiguration.openshift.io/role: worker
    38. Node Selector:
    39. Match Labels:
    40. node-role.kubernetes.io/worker:
    41. Paused: false
    42. Status:
    43. Conditions:
    44. Last Transition Time: 2019-12-19T02:03:27Z
    45. Message:
    46. Reason:
    47. Status: False
    48. Type: RenderDegraded
    49. Last Transition Time: 2019-12-19T02:03:43Z
    50. Message:
    51. Reason:
    52. Status: False
    53. Type: NodeDegraded
    54. Last Transition Time: 2019-12-19T02:03:43Z
    55. Message:
    56. Reason:
    57. Status: False
    58. Type: Degraded
    59. Last Transition Time: 2019-12-19T02:28:23Z
    60. Message:
    61. Reason:
    62. Status: False
    63. Type: Updated
    64. Last Transition Time: 2019-12-19T02:28:23Z
    65. Message: All nodes are updating to rendered-worker-f6819366eb455a401c42f8d96ab25c02
    66. Reason:
    67. Status: True
    68. Type: Updating
    69. Configuration:
    70. Name: rendered-worker-d9b3f4ffcfd65c30dcf591a0e8cf9b2e
    71. Source:
    72. API Version: machineconfiguration.openshift.io/v1
    73. Kind: MachineConfig
    74. Name: 00-worker
    75. API Version: machineconfiguration.openshift.io/v1
    76. Kind: MachineConfig
    77. Name: 01-worker-container-runtime
    78. API Version: machineconfiguration.openshift.io/v1
    79. Kind: MachineConfig
    80. Name: 01-worker-kubelet
    81. API Version: machineconfiguration.openshift.io/v1
    82. Kind: MachineConfig
    83. Name: 99-worker-92697796-2203-11ea-b48c-fa163e3940e5-registries
    84. API Version: machineconfiguration.openshift.io/v1
    85. Kind: MachineConfig
    86. Name: 99-worker-ssh
    87. Degraded Machine Count: 0
    88. Machine Count: 1
    89. Observed Generation: 3
    90. Ready Machine Count: 0
    91. Unavailable Machine Count: 1
    92. Updated Machine Count: 0
    93. Events: <none>
  2. Run the oc describe command again:

    1. $ oc describe machineconfigpool/worker

    Example output after the worker is updated

    1. ...
    2. Last Transition Time: 2019-12-19T04:53:09Z
    3. Message: All nodes are updated with rendered-worker-f6819366eb455a401c42f8d96ab25c02
    4. Reason:
    5. Status: True
    6. Type: Updated
    7. Last Transition Time: 2019-12-19T04:53:09Z
    8. Message:
    9. Reason:
    10. Status: False
    11. Type: Updating
    12. Configuration:
    13. Name: rendered-worker-f6819366eb455a401c42f8d96ab25c02
    14. Source:
    15. API Version: machineconfiguration.openshift.io/v1
    16. Kind: MachineConfig
    17. Name: 00-worker
    18. API Version: machineconfiguration.openshift.io/v1
    19. Kind: MachineConfig
    20. Name: 01-worker-container-runtime
    21. API Version: machineconfiguration.openshift.io/v1
    22. Kind: MachineConfig
    23. Name: 01-worker-kubelet
    24. API Version: machineconfiguration.openshift.io/v1
    25. Kind: MachineConfig
    26. Name: 51-worker-rh-registry-trust
    27. API Version: machineconfiguration.openshift.io/v1
    28. Kind: MachineConfig
    29. Name: 99-worker-92697796-2203-11ea-b48c-fa163e3940e5-registries
    30. API Version: machineconfiguration.openshift.io/v1
    31. Kind: MachineConfig
    32. Name: 99-worker-ssh
    33. Degraded Machine Count: 0
    34. Machine Count: 3
    35. Observed Generation: 4
    36. Ready Machine Count: 3
    37. Unavailable Machine Count: 0
    38. Updated Machine Count: 3
    39. ...

    The Observed Generation parameter shows an increased count based on the generation of the controller-produced configuration. This controller updates this value even if it fails to process the specification and generate a revision. The Configuration Source value points to the 51-worker-rh-registry-trust configuration.

  3. Confirm that the policy.json file exists with the following command:

    1. $ oc debug node/<node> -- chroot /host cat /etc/containers/policy.json

    Example output

    1. Starting pod/<node>-debug ...
    2. To use host binaries, run `chroot /host`
    3. {
    4. "default": [
    5. {
    6. "type": "insecureAcceptAnything"
    7. }
    8. ],
    9. "transports": {
    10. "docker": {
    11. "registry.access.redhat.com": [
    12. {
    13. "type": "signedBy",
    14. "keyType": "GPGKeys",
    15. "keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"
    16. }
    17. ],
    18. "registry.redhat.io": [
    19. {
    20. "type": "signedBy",
    21. "keyType": "GPGKeys",
    22. "keyPath": "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"
    23. }
    24. ]
    25. },
    26. "docker-daemon": {
    27. "": [
    28. {
    29. "type": "insecureAcceptAnything"
    30. }
    31. ]
    32. }
    33. }
    34. }
  4. Confirm that the registry.redhat.io.yaml file exists with the following command:

    1. $ oc debug node/<node> -- chroot /host cat /etc/containers/registries.d/registry.redhat.io.yaml

    Example output

    1. Starting pod/<node>-debug ...
    2. To use host binaries, run `chroot /host`
    3. docker:
    4. registry.redhat.io:
    5. sigstore: https://registry.redhat.io/containers/sigstore
  5. Confirm that the registry.access.redhat.com.yaml file exists with the following command:

    1. $ oc debug node/<node> -- chroot /host cat /etc/containers/registries.d/registry.access.redhat.com.yaml

    Example output

    1. Starting pod/<node>-debug ...
    2. To use host binaries, run `chroot /host`
    3. docker:
    4. registry.access.redhat.com:
    5. sigstore: https://access.redhat.com/webassets/docker/content/sigstore

Understanding the verification of container images lacking verifiable signatures

Each OKD release image is immutable and signed with a Red Hat production key. During an OKD update or installation, a release image might deploy container images that do not have verifiable signatures. Each signed release image digest is immutable. Each reference in the release image is to the immutable digest of another image, so the contents can be trusted transitively. In other words, the signature on the release image validates all release contents.

For example, the image references lacking a verifiable signature are contained in the signed OKD release image:

Example release info output

  1. $ oc adm release info quay.io/openshift-release-dev/ ocp-release@sha256:2309578b68c5666dad62aed696f1f9d778ae1a089ee461060ba7b9514b7ca417 -o pullspec (1)
  2. quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9aafb914d5d7d0dec4edd800d02f811d7383a7d49e500af548eab5d00c1bffdb (2)
1Signed release image SHA.
2Container image lacking a verifiable signature included in the release.

Automated verification during updates

Verification of signatures is automatic. The OpenShift Cluster Version Operator (CVO) verifies signatures on the release images during an OKD update. This is an internal process. An OKD installation or update fails if the automated verification fails.

Verification of signatures can also be done manually using the skopeo command-line utility.

Additional resources

Using skopeo to verify signatures of Red Hat container images

You can verify the signatures for container images included in an OKD release image by pulling those signatures from OCP release mirror site. Because the signatures on the mirror site are not in a format readily understood by Podman or CRI-O, you can use the skopeo standalone-verify command to verify that the your release images are signed by Red Hat.

Prerequisites

  • You have installed the skopeo command-line utility.

Procedure

  1. Get the full SHA for your release by running the following command:

    1. $ oc adm release info <release_version> \ (1)
    1Substitute <release_version> with your release number, for example, 4.14.3.

    Example output snippet

    1. ---
    2. Pull From: quay.io/openshift-release-dev/ocp-release@sha256:e73ab4b33a9c3ff00c9f800a38d69853ca0c4dfa5a88e3df331f66df8f18ec55
    3. ---
  2. Pull down the Red Hat release key by running the following command:

    1. $ curl -o pub.key https://access.redhat.com/security/data/fd431d51.txt
  3. Get the signature file for the specific release that you want to verify by running the following command:

    1. $ curl -o signature-1 https://mirror.openshift.com/pub/openshift-v4/signatures/openshift-release-dev/ocp-release/sha256%<sha_from_version>/signature-1 \ (1)
    1Replace <sha_from_version> with SHA value from the full link to the mirror site that matches the SHA of your release. For example, the link to the signature for the 4.12.23 release is https://mirror.openshift.com/pub/openshift-v4/signatures/openshift-release-dev/ocp-release/sha256%e73ab4b33a9c3ff00c9f800a38d69853ca0c4dfa5a88e3df331f66df8f18ec55/signature-1, and the SHA value is e73ab4b33a9c3ff00c9f800a38d69853ca0c4dfa5a88e3df331f66df8f18ec55.
  4. Get the manifest for the release image by running the following command:

    1. $ skopeo inspect --raw docker://<quay_link_to_release> > manifest.json \ (1)
    1Replace <quay_link_to_release> with the output of the oc adm release info command. For example, quay.io/openshift-release-dev/ocp-release@sha256:e73ab4b33a9c3ff00c9f800a38d69853ca0c4dfa5a88e3df331f66df8f18ec55.
  5. Use skopeo to verify the signature:

    1. $ skopeo standalone-verify manifest.json quay.io/openshift-release-dev/ocp-release:<release_number>-<arch> any signature-1 --public-key-file pub.key

    where:

    <release_number>

    Specifies the release number, for example 4.14.3.

    <arch>

    Specifies the architecture, for example x86_64.

    Example output

    1. Signature verified using fingerprint 567E347AD0044ADE55BA8A5F199E2F91FD431D51, digest sha256:e73ab4b33a9c3ff00c9f800a38d69853ca0c4dfa5a88e3df331f66df8f18ec55

Additional resources