diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-09 03:17:24 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-09 03:34:12 +0000 |
| commit | 52fc4ca7c75594fe8b3c92a9f88df19f8f4d0944 (patch) | |
| tree | f003083cf14ecb0543303ab0023d39fd3457eb78 /recipes-containers | |
| parent | 092aa81983335b2346a725eebd2a75fc785bb42b (diff) | |
| download | meta-virtualization-52fc4ca7c75594fe8b3c92a9f88df19f8f4d0944.tar.gz | |
container-registry: add target image TLS integration
Install CA certificates and registry configuration into target images
so they can pull from the secure registry at runtime.
docker-registry-config.bb: When CONTAINER_REGISTRY_SECURE=1, install
the CA cert to /etc/docker/certs.d/{host}/ca.crt instead of adding
insecure-registries to daemon.json. Translates localhost/127.0.0.1 to
10.0.2.2 for QEMU targets where the host registry is accessed via
slirp networking.
container-oci-registry-config.bb: Same secure mode support for
podman/CRI-O with insecure=false in registries.conf.
container-registry-ca.bb: New recipe that installs the CA certificate
to Docker, podman/CRI-O, and system trust store paths on the target.
container-cross-install.bbclass: Auto-add docker-registry-config or
container-oci-registry-config to IMAGE_INSTALL when
CONTAINER_REGISTRY_SECURE=1, based on the configured container engine.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers')
3 files changed, 278 insertions, 40 deletions
diff --git a/recipes-containers/container-registry/container-oci-registry-config.bb b/recipes-containers/container-registry/container-oci-registry-config.bb index ba8cf4c3..294defc3 100644 --- a/recipes-containers/container-registry/container-oci-registry-config.bb +++ b/recipes-containers/container-registry/container-oci-registry-config.bb | |||
| @@ -17,21 +17,27 @@ | |||
| 17 | # See: docker-registry-config.bb for Docker configuration | 17 | # See: docker-registry-config.bb for Docker configuration |
| 18 | # | 18 | # |
| 19 | # This recipe creates a drop-in configuration file for accessing a custom | 19 | # This recipe creates a drop-in configuration file for accessing a custom |
| 20 | # container registry. It is completely OPT-IN and does not modify any | 20 | # container registry. It supports both insecure (HTTP) and secure (HTTPS with TLS) |
| 21 | # existing configuration files. | 21 | # modes. It is completely OPT-IN and does not modify any existing configuration files. |
| 22 | # | 22 | # |
| 23 | # IMPORTANT: This recipe: | 23 | # IMPORTANT: This recipe: |
| 24 | # - Does NOT modify docker-distribution or container-host-config | 24 | # - Does NOT modify docker-distribution or container-host-config |
| 25 | # - Does NOT clobber public registry access (docker.io, quay.io, etc.) | 25 | # - Does NOT clobber public registry access (docker.io, quay.io, etc.) |
| 26 | # - Uses drop-in files in /etc/containers/registries.conf.d/ | 26 | # - Uses drop-in files in /etc/containers/registries.conf.d/ |
| 27 | # - Skips entirely if CONTAINER_REGISTRY_URL is not set | 27 | # - Skips entirely if CONTAINER_REGISTRY_URL is not set |
| 28 | # - In secure mode: installs CA cert to /etc/containers/certs.d/{registry}/ | ||
| 28 | # | 29 | # |
| 29 | # Usage: | 30 | # Usage: |
| 30 | # # In local.conf or image recipe: | 31 | # # Insecure mode (HTTP): |
| 31 | # CONTAINER_REGISTRY_URL = "localhost:5000" | 32 | # CONTAINER_REGISTRY_URL = "localhost:5000" |
| 32 | # CONTAINER_REGISTRY_INSECURE = "1" | 33 | # CONTAINER_REGISTRY_INSECURE = "1" |
| 33 | # IMAGE_FEATURES += "container-registry" | 34 | # IMAGE_FEATURES += "container-registry" |
| 34 | # | 35 | # |
| 36 | # # Secure mode (HTTPS with TLS): | ||
| 37 | # CONTAINER_REGISTRY_SECURE = "1" | ||
| 38 | # CONTAINER_REGISTRY_URL = "localhost:5000" | ||
| 39 | # IMAGE_FEATURES += "container-registry" | ||
| 40 | # | ||
| 35 | # The IMAGE_FEATURES mechanism auto-selects this recipe for Podman/CRI-O | 41 | # The IMAGE_FEATURES mechanism auto-selects this recipe for Podman/CRI-O |
| 36 | # or docker-registry-config for Docker based on VIRTUAL-RUNTIME_container_engine. | 42 | # or docker-registry-config for Docker based on VIRTUAL-RUNTIME_container_engine. |
| 37 | # | 43 | # |
| @@ -40,6 +46,7 @@ | |||
| 40 | SUMMARY = "Configure custom container registry for Podman/Skopeo/Buildah (opt-in)" | 46 | SUMMARY = "Configure custom container registry for Podman/Skopeo/Buildah (opt-in)" |
| 41 | DESCRIPTION = "Adds drop-in configuration for Podman, Skopeo, Buildah, and CRI-O. \ | 47 | DESCRIPTION = "Adds drop-in configuration for Podman, Skopeo, Buildah, and CRI-O. \ |
| 42 | NOT for Docker (use docker-registry-config for Docker). \ | 48 | NOT for Docker (use docker-registry-config for Docker). \ |
| 49 | Supports both insecure (HTTP) and secure (HTTPS with TLS) modes. \ | ||
| 43 | Does NOT modify existing registries.conf - creates a separate file in \ | 50 | Does NOT modify existing registries.conf - creates a separate file in \ |
| 44 | registries.conf.d/ that is merged at runtime. Public registries remain accessible. \ | 51 | registries.conf.d/ that is merged at runtime. Public registries remain accessible. \ |
| 45 | This recipe is opt-in: requires CONTAINER_REGISTRY_URL to be set. \ | 52 | This recipe is opt-in: requires CONTAINER_REGISTRY_URL to be set. \ |
| @@ -48,12 +55,14 @@ Use IMAGE_FEATURES container-registry to auto-select based on container engine." | |||
| 48 | LICENSE = "MIT" | 55 | LICENSE = "MIT" |
| 49 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" | 56 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" |
| 50 | 57 | ||
| 58 | inherit allarch container-registry | ||
| 59 | |||
| 51 | # User MUST set these - recipe skips otherwise | 60 | # User MUST set these - recipe skips otherwise |
| 52 | CONTAINER_REGISTRY_URL ?= "" | ||
| 53 | CONTAINER_REGISTRY_INSECURE ?= "0" | ||
| 54 | CONTAINER_REGISTRY_SEARCH_FIRST ?= "1" | 61 | CONTAINER_REGISTRY_SEARCH_FIRST ?= "1" |
| 55 | 62 | ||
| 56 | inherit allarch | 63 | # Path to OCI auth config (for baked credentials) |
| 64 | # NOT stored in bitbake - should point to external file | ||
| 65 | CONTAINER_REGISTRY_AUTHFILE ?= "" | ||
| 57 | 66 | ||
| 58 | # Skip recipe entirely if not configured | 67 | # Skip recipe entirely if not configured |
| 59 | # User must explicitly set CONTAINER_REGISTRY_URL to enable | 68 | # User must explicitly set CONTAINER_REGISTRY_URL to enable |
| @@ -61,20 +70,54 @@ python() { | |||
| 61 | registry = d.getVar('CONTAINER_REGISTRY_URL') | 70 | registry = d.getVar('CONTAINER_REGISTRY_URL') |
| 62 | if not registry: | 71 | if not registry: |
| 63 | raise bb.parse.SkipRecipe("CONTAINER_REGISTRY_URL not set - recipe is opt-in only") | 72 | raise bb.parse.SkipRecipe("CONTAINER_REGISTRY_URL not set - recipe is opt-in only") |
| 73 | |||
| 74 | # Check for conflicting settings | ||
| 75 | secure = d.getVar('CONTAINER_REGISTRY_SECURE') == '1' | ||
| 76 | insecure = d.getVar('CONTAINER_REGISTRY_INSECURE') == '1' | ||
| 77 | |||
| 78 | if secure and insecure: | ||
| 79 | bb.fatal("CONTAINER_REGISTRY_SECURE='1' and CONTAINER_REGISTRY_INSECURE='1' cannot both be set. " | ||
| 80 | "Use secure mode (TLS+auth) OR insecure mode (HTTP), not both.") | ||
| 81 | |||
| 82 | # In secure mode, depend on PKI generation | ||
| 83 | if secure: | ||
| 84 | d.appendVarFlag('do_install', 'depends', ' container-registry-index:do_generate_registry_script') | ||
| 64 | } | 85 | } |
| 65 | 86 | ||
| 66 | python do_install() { | 87 | python do_install() { |
| 67 | import os | 88 | import os |
| 89 | import shutil | ||
| 68 | 90 | ||
| 69 | registry = d.getVar('CONTAINER_REGISTRY_URL') | 91 | registry = d.getVar('CONTAINER_REGISTRY_URL') |
| 70 | insecure = d.getVar('CONTAINER_REGISTRY_INSECURE') == "1" | 92 | insecure = d.getVar('CONTAINER_REGISTRY_INSECURE') == "1" |
| 93 | secure = d.getVar('CONTAINER_REGISTRY_SECURE') == "1" | ||
| 71 | search_first = d.getVar('CONTAINER_REGISTRY_SEARCH_FIRST') == "1" | 94 | search_first = d.getVar('CONTAINER_REGISTRY_SEARCH_FIRST') == "1" |
| 95 | ca_cert = d.getVar('CONTAINER_REGISTRY_CA_CERT') | ||
| 96 | authfile = d.getVar('CONTAINER_REGISTRY_AUTHFILE') or '' | ||
| 97 | |||
| 98 | # Extract registry host (strip any path) | ||
| 99 | registry_host = registry.split('/')[0] if '/' in registry else registry | ||
| 72 | 100 | ||
| 73 | dest = d.getVar('D') | 101 | dest = d.getVar('D') |
| 74 | confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'), | 102 | confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'), |
| 75 | 'containers', 'registries.conf.d') | 103 | 'containers', 'registries.conf.d') |
| 76 | os.makedirs(confdir, exist_ok=True) | 104 | os.makedirs(confdir, exist_ok=True) |
| 77 | 105 | ||
| 106 | # Install CA cert in secure mode | ||
| 107 | if secure: | ||
| 108 | if os.path.exists(ca_cert): | ||
| 109 | cert_dir = os.path.join(dest, 'etc/containers/certs.d', registry_host) | ||
| 110 | os.makedirs(cert_dir, exist_ok=True) | ||
| 111 | shutil.copy(ca_cert, os.path.join(cert_dir, 'ca.crt')) | ||
| 112 | bb.note(f"Installed CA certificate for registry: {registry_host}") | ||
| 113 | else: | ||
| 114 | bb.warn(f"Secure mode enabled but CA certificate not found at {ca_cert}") | ||
| 115 | bb.warn("Run 'container-registry.sh start' to generate PKI, then rebuild this package") | ||
| 116 | |||
| 117 | # In secure mode, insecure should be false | ||
| 118 | if secure: | ||
| 119 | insecure = False | ||
| 120 | |||
| 78 | # Generate drop-in config | 121 | # Generate drop-in config |
| 79 | # Filename starts with 50- so it's processed after base config but | 122 | # Filename starts with 50- so it's processed after base config but |
| 80 | # can be overridden by higher-numbered files | 123 | # can be overridden by higher-numbered files |
| @@ -82,11 +125,17 @@ python do_install() { | |||
| 82 | 125 | ||
| 83 | with open(config_path, 'w') as f: | 126 | with open(config_path, 'w') as f: |
| 84 | f.write(f"# Custom container registry: {registry}\n") | 127 | f.write(f"# Custom container registry: {registry}\n") |
| 85 | f.write(f"# Generated by container-registry-config recipe\n") | 128 | f.write(f"# Generated by container-oci-registry-config recipe\n") |
| 86 | f.write(f"# This is ADDITIVE - base registries.conf is unchanged\n") | 129 | f.write(f"# This is ADDITIVE - base registries.conf is unchanged\n") |
| 87 | f.write(f"# Public registries (docker.io, quay.io) remain accessible\n") | 130 | f.write(f"# Public registries (docker.io, quay.io) remain accessible\n") |
| 88 | f.write(f"#\n") | 131 | f.write(f"#\n") |
| 89 | f.write(f"# To remove: uninstall container-registry-config package\n") | 132 | if secure: |
| 133 | f.write(f"# Mode: secure (TLS with CA certificate verification)\n") | ||
| 134 | f.write(f"# CA cert: /etc/containers/certs.d/{registry_host}/ca.crt\n") | ||
| 135 | else: | ||
| 136 | f.write(f"# Mode: insecure (HTTP or untrusted TLS)\n") | ||
| 137 | f.write(f"#\n") | ||
| 138 | f.write(f"# To remove: uninstall container-oci-registry-config package\n") | ||
| 90 | f.write(f"# or delete this file\n\n") | 139 | f.write(f"# or delete this file\n\n") |
| 91 | 140 | ||
| 92 | if search_first: | 141 | if search_first: |
| @@ -95,17 +144,40 @@ python do_install() { | |||
| 95 | f.write(f"# Search this registry for unqualified image names\n") | 144 | f.write(f"# Search this registry for unqualified image names\n") |
| 96 | f.write(f'unqualified-search-registries = ["{registry}"]\n\n') | 145 | f.write(f'unqualified-search-registries = ["{registry}"]\n\n') |
| 97 | 146 | ||
| 147 | # Always create registry entry to set insecure flag explicitly | ||
| 148 | f.write(f'[[registry]]\n') | ||
| 149 | f.write(f'location = "{registry_host}"\n') | ||
| 98 | if insecure: | 150 | if insecure: |
| 99 | # Mark registry as insecure (HTTP or self-signed TLS) | ||
| 100 | f.write(f"# Registry uses HTTP or has untrusted TLS certificate\n") | ||
| 101 | f.write(f'[[registry]]\n') | ||
| 102 | f.write(f'location = "{registry}"\n') | ||
| 103 | f.write(f'insecure = true\n') | 151 | f.write(f'insecure = true\n') |
| 152 | else: | ||
| 153 | f.write(f'insecure = false\n') | ||
| 104 | 154 | ||
| 105 | bb.note(f"Created registry config for {registry} (insecure={insecure})") | 155 | # Install authfile if provided (for baked credentials) |
| 156 | if authfile and os.path.exists(authfile): | ||
| 157 | containers_dir = os.path.join(dest, 'etc/containers') | ||
| 158 | os.makedirs(containers_dir, exist_ok=True) | ||
| 159 | auth_json = os.path.join(containers_dir, 'auth.json') | ||
| 160 | shutil.copy(authfile, auth_json) | ||
| 161 | os.chmod(auth_json, 0o600) | ||
| 162 | bb.note(f"Installed OCI auth config from {authfile}") | ||
| 163 | |||
| 164 | mode = "secure" if secure else ("insecure" if insecure else "default") | ||
| 165 | bb.note(f"Created registry config for {registry} (mode={mode})") | ||
| 106 | } | 166 | } |
| 107 | 167 | ||
| 108 | FILES:${PN} = "${sysconfdir}/containers/registries.conf.d" | 168 | FILES:${PN} = " \ |
| 169 | ${sysconfdir}/containers/registries.conf.d \ | ||
| 170 | ${sysconfdir}/containers/certs.d/*/ca.crt \ | ||
| 171 | ${sysconfdir}/containers/auth.json \ | ||
| 172 | " | ||
| 173 | |||
| 174 | # Ensure proper permissions on auth file | ||
| 175 | pkg_postinst:${PN}() { | ||
| 176 | #!/bin/sh | ||
| 177 | if [ -f $D/etc/containers/auth.json ]; then | ||
| 178 | chmod 600 $D/etc/containers/auth.json | ||
| 179 | fi | ||
| 180 | } | ||
| 109 | 181 | ||
| 110 | # Soft dependency - works with or without container-host-config | 182 | # Soft dependency - works with or without container-host-config |
| 111 | # If container-host-config is installed, our drop-in extends it | 183 | # If container-host-config is installed, our drop-in extends it |
diff --git a/recipes-containers/container-registry/container-registry-ca.bb b/recipes-containers/container-registry/container-registry-ca.bb new file mode 100644 index 00000000..85bb206f --- /dev/null +++ b/recipes-containers/container-registry/container-registry-ca.bb | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | # SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield | ||
| 2 | # | ||
| 3 | # SPDX-License-Identifier: MIT | ||
| 4 | # | ||
| 5 | # container-registry-ca.bb | ||
| 6 | # ============================================================================ | ||
| 7 | # Install CA certificate for secure container registry on target images. | ||
| 8 | # | ||
| 9 | # This recipe installs the CA certificate generated during | ||
| 10 | # container-registry-index:do_generate_registry_script to the appropriate | ||
| 11 | # locations for Docker, Podman/CRI-O, and system trust. | ||
| 12 | # | ||
| 13 | # Prerequisites: | ||
| 14 | # 1. Enable secure mode: CONTAINER_REGISTRY_SECURE = "1" | ||
| 15 | # 2. PKI is auto-generated when building this package | ||
| 16 | # | ||
| 17 | # Usage: | ||
| 18 | # IMAGE_INSTALL:append = " container-registry-ca" | ||
| 19 | # | ||
| 20 | # Installed files: | ||
| 21 | # /etc/docker/certs.d/{registry}/ca.crt - Docker daemon trust | ||
| 22 | # /etc/containers/certs.d/{registry}/ca.crt - Podman/CRI-O trust | ||
| 23 | # /usr/local/share/ca-certificates/container-registry-ca.crt - System trust | ||
| 24 | # | ||
| 25 | # ============================================================================ | ||
| 26 | |||
| 27 | SUMMARY = "CA certificate for secure container registry" | ||
| 28 | DESCRIPTION = "Installs the CA certificate for TLS verification when pulling from the local container registry" | ||
| 29 | LICENSE = "MIT" | ||
| 30 | LIC_FILES_CHKSUM = "file://${COREBASE}/meta/COPYING.MIT;md5=3da9cfbcb788c80a0384361b4de20420" | ||
| 31 | |||
| 32 | inherit container-registry | ||
| 33 | |||
| 34 | # Only build if secure mode is enabled | ||
| 35 | python () { | ||
| 36 | secure = d.getVar('CONTAINER_REGISTRY_SECURE') | ||
| 37 | if secure != '1': | ||
| 38 | raise bb.parse.SkipRecipe("CONTAINER_REGISTRY_SECURE is not '1' - secure mode not enabled") | ||
| 39 | } | ||
| 40 | |||
| 41 | # No source files - we use the generated CA cert | ||
| 42 | SRC_URI = "" | ||
| 43 | |||
| 44 | do_configure[noexec] = "1" | ||
| 45 | do_compile[noexec] = "1" | ||
| 46 | |||
| 47 | # Ensure PKI is generated before we try to install the CA cert | ||
| 48 | do_install[depends] += "container-registry-index:do_generate_registry_script" | ||
| 49 | |||
| 50 | python do_install() { | ||
| 51 | import os | ||
| 52 | import shutil | ||
| 53 | |||
| 54 | d_dir = d.getVar('D') | ||
| 55 | ca_cert = d.getVar('CONTAINER_REGISTRY_CA_CERT') | ||
| 56 | registry_url = d.getVar('CONTAINER_REGISTRY_URL') | ||
| 57 | |||
| 58 | # Extract registry host (strip port) | ||
| 59 | registry_host = registry_url.split('/')[0] if '/' in registry_url else registry_url | ||
| 60 | |||
| 61 | if not os.path.exists(ca_cert): | ||
| 62 | bb.fatal(f"CA certificate not found at {ca_cert}. " | ||
| 63 | "This should have been auto-generated. Check container-registry-index:do_generate_registry_script logs.") | ||
| 64 | |||
| 65 | # Install for Docker: /etc/docker/certs.d/{registry}/ca.crt | ||
| 66 | docker_cert_dir = os.path.join(d_dir, 'etc/docker/certs.d', registry_host) | ||
| 67 | os.makedirs(docker_cert_dir, exist_ok=True) | ||
| 68 | shutil.copy(ca_cert, os.path.join(docker_cert_dir, 'ca.crt')) | ||
| 69 | |||
| 70 | # Install for Podman/CRI-O: /etc/containers/certs.d/{registry}/ca.crt | ||
| 71 | containers_cert_dir = os.path.join(d_dir, 'etc/containers/certs.d', registry_host) | ||
| 72 | os.makedirs(containers_cert_dir, exist_ok=True) | ||
| 73 | shutil.copy(ca_cert, os.path.join(containers_cert_dir, 'ca.crt')) | ||
| 74 | |||
| 75 | # Install for system trust: /usr/local/share/ca-certificates/ | ||
| 76 | system_ca_dir = os.path.join(d_dir, 'usr/local/share/ca-certificates') | ||
| 77 | os.makedirs(system_ca_dir, exist_ok=True) | ||
| 78 | shutil.copy(ca_cert, os.path.join(system_ca_dir, 'container-registry-ca.crt')) | ||
| 79 | |||
| 80 | bb.note(f"Installed CA certificate for registry: {registry_host}") | ||
| 81 | } | ||
| 82 | |||
| 83 | # Package files | ||
| 84 | FILES:${PN} = " \ | ||
| 85 | ${sysconfdir}/docker/certs.d/*/ca.crt \ | ||
| 86 | ${sysconfdir}/containers/certs.d/*/ca.crt \ | ||
| 87 | /usr/local/share/ca-certificates/container-registry-ca.crt \ | ||
| 88 | " | ||
| 89 | |||
| 90 | # Run update-ca-certificates after install if available | ||
| 91 | pkg_postinst:${PN}() { | ||
| 92 | #!/bin/sh | ||
| 93 | if [ -x /usr/sbin/update-ca-certificates ]; then | ||
| 94 | /usr/sbin/update-ca-certificates 2>/dev/null || true | ||
| 95 | fi | ||
| 96 | } | ||
| 97 | |||
| 98 | RDEPENDS:${PN} = "" | ||
diff --git a/recipes-containers/container-registry/docker-registry-config.bb b/recipes-containers/container-registry/docker-registry-config.bb index e03cd3eb..0e8d66ad 100644 --- a/recipes-containers/container-registry/docker-registry-config.bb +++ b/recipes-containers/container-registry/docker-registry-config.bb | |||
| @@ -10,46 +10,55 @@ | |||
| 10 | # FOR DOCKER ONLY - creates /etc/docker/daemon.json | 10 | # FOR DOCKER ONLY - creates /etc/docker/daemon.json |
| 11 | # | 11 | # |
| 12 | # NOT for Podman/Skopeo/Buildah - they use /etc/containers/registries.conf.d/ | 12 | # NOT for Podman/Skopeo/Buildah - they use /etc/containers/registries.conf.d/ |
| 13 | # See: container-registry-config.bb for Podman/Skopeo/Buildah | 13 | # See: container-oci-registry-config.bb for Podman/Skopeo/Buildah |
| 14 | # | 14 | # |
| 15 | # This recipe creates daemon.json for Docker to access insecure registries. | 15 | # This recipe creates daemon.json for Docker to access registries. |
| 16 | # It is completely OPT-IN and requires explicit configuration. | 16 | # It supports both insecure (HTTP) and secure (HTTPS with TLS) modes. |
| 17 | # | 17 | # |
| 18 | # NOTE: Docker does not support "default registry" like our vdkr transform. | 18 | # NOTE: Docker does not support "default registry" like our vdkr transform. |
| 19 | # Users must still use fully qualified image names unless using Docker Hub. | 19 | # Users must still use fully qualified image names unless using Docker Hub. |
| 20 | # This config only handles insecure registry trust. | ||
| 21 | # | 20 | # |
| 22 | # IMPORTANT: This recipe: | 21 | # IMPORTANT: This recipe: |
| 23 | # - Skips entirely if DOCKER_REGISTRY_INSECURE is not set | 22 | # - Skips entirely if neither insecure nor secure registry is configured |
| 24 | # - Creates /etc/docker/daemon.json (will be merged if docker recipe | 23 | # - Creates /etc/docker/daemon.json |
| 25 | # also creates one, or may need RCONFLICTS handling) | 24 | # - In secure mode: installs CA cert to /etc/docker/certs.d/{registry}/ |
| 25 | # - In insecure mode: adds registry to insecure-registries list | ||
| 26 | # | 26 | # |
| 27 | # Usage: | 27 | # Usage: |
| 28 | # # In local.conf or image recipe: | 28 | # # Insecure mode (HTTP): |
| 29 | # DOCKER_REGISTRY_INSECURE = "10.0.2.2:5000 myregistry.local:5000" | 29 | # DOCKER_REGISTRY_INSECURE = "10.0.2.2:5000 myregistry.local:5000" |
| 30 | # IMAGE_FEATURES += "container-registry" | 30 | # IMAGE_FEATURES += "container-registry" |
| 31 | # | 31 | # |
| 32 | # # Secure mode (HTTPS with TLS): | ||
| 33 | # CONTAINER_REGISTRY_SECURE = "1" | ||
| 34 | # CONTAINER_REGISTRY_URL = "localhost:5000" | ||
| 35 | # IMAGE_FEATURES += "container-registry" | ||
| 36 | # | ||
| 32 | # The IMAGE_FEATURES mechanism auto-selects this recipe for Docker | 37 | # The IMAGE_FEATURES mechanism auto-selects this recipe for Docker |
| 33 | # or container-oci-registry-config for Podman/CRI-O based on | 38 | # or container-oci-registry-config for Podman/CRI-O based on |
| 34 | # VIRTUAL-RUNTIME_container_engine. | 39 | # VIRTUAL-RUNTIME_container_engine. |
| 35 | # | 40 | # |
| 36 | # =========================================================================== | 41 | # =========================================================================== |
| 37 | 42 | ||
| 38 | SUMMARY = "Configure insecure container registries for Docker daemon (opt-in)" | 43 | SUMMARY = "Configure container registry for Docker daemon (opt-in)" |
| 39 | DESCRIPTION = "Creates /etc/docker/daemon.json with insecure-registries config. \ | 44 | DESCRIPTION = "Creates /etc/docker/daemon.json with registry config. \ |
| 40 | FOR DOCKER ONLY - not for Podman/Skopeo (use container-oci-registry-config for those). \ | 45 | FOR DOCKER ONLY - not for Podman/Skopeo (use container-oci-registry-config for those). \ |
| 41 | This recipe is opt-in: requires DOCKER_REGISTRY_INSECURE to be set. \ | 46 | Supports both insecure (HTTP) and secure (HTTPS with TLS) modes. \ |
| 42 | Use IMAGE_FEATURES container-registry to auto-select based on container engine." | 47 | Use IMAGE_FEATURES container-registry to auto-select based on container engine." |
| 43 | 48 | ||
| 44 | LICENSE = "MIT" | 49 | LICENSE = "MIT" |
| 45 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" | 50 | LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302" |
| 46 | 51 | ||
| 52 | inherit allarch container-registry | ||
| 53 | |||
| 47 | # Space-separated list of insecure registries | 54 | # Space-separated list of insecure registries |
| 48 | # Example: "10.0.2.2:5000 myregistry.local:5000" | 55 | # Example: "10.0.2.2:5000 myregistry.local:5000" |
| 49 | # Can also use runtime-agnostic CONTAINER_REGISTRY_URL + CONTAINER_REGISTRY_INSECURE | 56 | # Can also use runtime-agnostic CONTAINER_REGISTRY_URL + CONTAINER_REGISTRY_INSECURE |
| 50 | DOCKER_REGISTRY_INSECURE ?= "" | 57 | DOCKER_REGISTRY_INSECURE ?= "" |
| 51 | CONTAINER_REGISTRY_URL ?= "" | 58 | |
| 52 | CONTAINER_REGISTRY_INSECURE ?= "" | 59 | # Path to Docker auth config (for baked credentials) |
| 60 | # NOT stored in bitbake - should point to external file | ||
| 61 | CONTAINER_REGISTRY_AUTHFILE ?= "" | ||
| 53 | 62 | ||
| 54 | def get_insecure_registries(d): | 63 | def get_insecure_registries(d): |
| 55 | """Get insecure registries from either Docker-specific or generic config""" | 64 | """Get insecure registries from either Docker-specific or generic config""" |
| @@ -64,40 +73,99 @@ def get_insecure_registries(d): | |||
| 64 | return registry_url.strip() | 73 | return registry_url.strip() |
| 65 | return "" | 74 | return "" |
| 66 | 75 | ||
| 67 | inherit allarch | 76 | def is_secure_mode(d): |
| 77 | """Check if secure registry mode is enabled""" | ||
| 78 | return d.getVar('CONTAINER_REGISTRY_SECURE') == '1' | ||
| 68 | 79 | ||
| 69 | # Skip recipe entirely if not configured | 80 | # Skip recipe entirely if not configured |
| 70 | python() { | 81 | python() { |
| 82 | secure = is_secure_mode(d) | ||
| 71 | registries = get_insecure_registries(d) | 83 | registries = get_insecure_registries(d) |
| 72 | if not registries: | 84 | |
| 73 | raise bb.parse.SkipRecipe("No insecure registry configured - recipe is opt-in only") | 85 | # Check for conflicting settings |
| 86 | if secure and registries: | ||
| 87 | bb.fatal("CONTAINER_REGISTRY_SECURE='1' conflicts with insecure registry settings. " | ||
| 88 | "Use secure mode (TLS+auth) OR insecure mode (HTTP), not both.") | ||
| 89 | |||
| 90 | if not secure and not registries: | ||
| 91 | raise bb.parse.SkipRecipe("No registry configured - recipe is opt-in only") | ||
| 92 | |||
| 93 | # In secure mode, depend on PKI generation | ||
| 94 | if secure: | ||
| 95 | d.appendVarFlag('do_install', 'depends', ' container-registry-index:do_generate_registry_script') | ||
| 74 | } | 96 | } |
| 75 | 97 | ||
| 76 | python do_install() { | 98 | python do_install() { |
| 77 | import os | 99 | import os |
| 78 | import json | 100 | import json |
| 79 | 101 | import shutil | |
| 80 | registries = get_insecure_registries(d).split() | ||
| 81 | 102 | ||
| 82 | dest = d.getVar('D') | 103 | dest = d.getVar('D') |
| 83 | confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'), 'docker') | 104 | confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'), 'docker') |
| 84 | os.makedirs(confdir, exist_ok=True) | 105 | os.makedirs(confdir, exist_ok=True) |
| 85 | 106 | ||
| 86 | config_path = os.path.join(confdir, 'daemon.json') | 107 | secure = is_secure_mode(d) |
| 108 | registries = get_insecure_registries(d).split() | ||
| 109 | ca_cert = d.getVar('CONTAINER_REGISTRY_CA_CERT') | ||
| 110 | registry_url = d.getVar('CONTAINER_REGISTRY_URL') or '' | ||
| 111 | authfile = d.getVar('CONTAINER_REGISTRY_AUTHFILE') or '' | ||
| 112 | |||
| 113 | config = {} | ||
| 114 | |||
| 115 | if secure: | ||
| 116 | # Secure mode: install CA cert, no insecure-registries | ||
| 117 | # Translate localhost/127.0.0.1 to 10.0.2.2 for QEMU slirp networking | ||
| 118 | qemu_url = registry_url.replace('localhost', '10.0.2.2').replace('127.0.0.1', '10.0.2.2') | ||
| 119 | registry_host = qemu_url.split('/')[0] if '/' in qemu_url else qemu_url | ||
| 120 | |||
| 121 | if os.path.exists(ca_cert): | ||
| 122 | # Install CA cert to /etc/docker/certs.d/{registry}/ca.crt | ||
| 123 | cert_dir = os.path.join(dest, 'etc/docker/certs.d', registry_host) | ||
| 124 | os.makedirs(cert_dir, exist_ok=True) | ||
| 125 | shutil.copy(ca_cert, os.path.join(cert_dir, 'ca.crt')) | ||
| 126 | bb.note(f"Installed CA certificate for registry: {registry_host}") | ||
| 127 | else: | ||
| 128 | bb.warn(f"Secure mode enabled but CA certificate not found at {ca_cert}") | ||
| 129 | bb.warn("Run 'container-registry.sh start' to generate PKI, then rebuild this package") | ||
| 130 | |||
| 131 | # daemon.json can be empty or minimal in secure mode | ||
| 132 | # (no insecure-registries needed when using TLS) | ||
| 133 | bb.note("Secure mode: Docker will use TLS verification with installed CA cert") | ||
| 134 | else: | ||
| 135 | # Insecure mode: add to insecure-registries | ||
| 136 | if registries: | ||
| 137 | config["insecure-registries"] = registries | ||
| 138 | bb.note(f"Created Docker config with insecure registries: {registries}") | ||
| 87 | 139 | ||
| 88 | # Create daemon.json | 140 | # Install authfile if provided (for baked credentials) |
| 89 | config = { | 141 | if authfile and os.path.exists(authfile): |
| 90 | "insecure-registries": registries | 142 | docker_dir = os.path.join(dest, 'root/.docker') |
| 91 | } | 143 | os.makedirs(docker_dir, mode=0o700, exist_ok=True) |
| 144 | config_json = os.path.join(docker_dir, 'config.json') | ||
| 145 | shutil.copy(authfile, config_json) | ||
| 146 | os.chmod(config_json, 0o600) | ||
| 147 | bb.note(f"Installed Docker auth config from {authfile}") | ||
| 92 | 148 | ||
| 149 | # Write daemon.json (may be empty in secure mode) | ||
| 150 | config_path = os.path.join(confdir, 'daemon.json') | ||
| 93 | with open(config_path, 'w') as f: | 151 | with open(config_path, 'w') as f: |
| 94 | json.dump(config, f, indent=2) | 152 | json.dump(config, f, indent=2) |
| 95 | f.write("\n") | 153 | f.write("\n") |
| 96 | |||
| 97 | bb.note(f"Created Docker config with insecure registries: {registries}") | ||
| 98 | } | 154 | } |
| 99 | 155 | ||
| 100 | FILES:${PN} = "${sysconfdir}/docker/daemon.json" | 156 | FILES:${PN} = " \ |
| 157 | ${sysconfdir}/docker/daemon.json \ | ||
| 158 | ${sysconfdir}/docker/certs.d/*/ca.crt \ | ||
| 159 | /root/.docker/config.json \ | ||
| 160 | " | ||
| 161 | |||
| 162 | # Ensure proper permissions on auth file | ||
| 163 | pkg_postinst:${PN}() { | ||
| 164 | #!/bin/sh | ||
| 165 | if [ -f $D/root/.docker/config.json ]; then | ||
| 166 | chmod 600 $D/root/.docker/config.json | ||
| 167 | fi | ||
| 168 | } | ||
| 101 | 169 | ||
| 102 | # Docker must be installed for this to be useful | 170 | # Docker must be installed for this to be useful |
| 103 | RDEPENDS:${PN} = "docker" | 171 | RDEPENDS:${PN} = "docker" |
