summaryrefslogtreecommitdiffstats
path: root/recipes-containers/container-registry/container-oci-registry-config.bb
blob: 5161f0a38d59afb176ec7516096c7ce92b36de05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
#
# SPDX-License-Identifier: MIT
#
# container-oci-registry-config.bb
# ===========================================================================
# Configure custom container registry for OCI runtimes (OPT-IN)
# ===========================================================================
#
# FOR OCI-COMPATIBLE RUNTIMES (use /etc/containers/registries.conf.d/):
#   - Podman
#   - Skopeo
#   - Buildah
#   - CRI-O
#
# NOT FOR DOCKER - Docker uses /etc/docker/daemon.json
#   See: docker-registry-config.bb for Docker configuration
#
# This recipe creates a drop-in configuration file for accessing a custom
# container registry. It supports both insecure (HTTP) and secure (HTTPS with TLS)
# modes. It is completely OPT-IN and does not modify any existing configuration files.
#
# IMPORTANT: This recipe:
#   - Does NOT modify docker-distribution or container-host-config
#   - Does NOT clobber public registry access (docker.io, quay.io, etc.)
#   - Uses drop-in files in /etc/containers/registries.conf.d/
#   - Skips entirely if CONTAINER_REGISTRY_URL is not set
#   - In secure mode: installs CA cert to /etc/containers/certs.d/{registry}/
#
# Usage:
#   # Insecure mode (HTTP):
#   CONTAINER_REGISTRY_URL = "localhost:5000"
#   CONTAINER_REGISTRY_INSECURE = "1"
#   IMAGE_FEATURES += "container-registry"
#
#   # Secure mode (HTTPS with TLS):
#   CONTAINER_REGISTRY_SECURE = "1"
#   CONTAINER_REGISTRY_URL = "localhost:5000"
#   IMAGE_FEATURES += "container-registry"
#
#   The IMAGE_FEATURES mechanism auto-selects this recipe for Podman/CRI-O
#   or docker-registry-config for Docker based on VIRTUAL-RUNTIME_container_engine.
#
# ===========================================================================

SUMMARY = "Configure custom container registry for Podman/Skopeo/Buildah (opt-in)"
DESCRIPTION = "Adds drop-in configuration for Podman, Skopeo, Buildah, and CRI-O. \
NOT for Docker (use docker-registry-config for Docker). \
Supports both insecure (HTTP) and secure (HTTPS with TLS) modes. \
Does NOT modify existing registries.conf - creates a separate file in \
registries.conf.d/ that is merged at runtime. Public registries remain accessible. \
This recipe is opt-in: requires CONTAINER_REGISTRY_URL to be set. \
Use IMAGE_FEATURES container-registry to auto-select based on container engine."

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

inherit allarch container-registry

# User MUST set these - recipe skips otherwise
CONTAINER_REGISTRY_SEARCH_FIRST ?= "1"

# Path to OCI auth config (for baked credentials)
# NOT stored in bitbake - should point to external file
CONTAINER_REGISTRY_AUTHFILE ?= ""

# OCI runtime configuration for Podman
# Runtime name (e.g. "vxn")  creates a containers.conf.d drop-in
PODMAN_OCI_RUNTIME ?= ""
# Path to OCI runtime binary (e.g. "/usr/bin/vxn-oci-runtime")
PODMAN_OCI_RUNTIME_PATH ?= ""

# Skip recipe entirely if not configured
# User must explicitly set CONTAINER_REGISTRY_URL to enable
python() {
    registry = d.getVar('CONTAINER_REGISTRY_URL')
    oci_runtime = (d.getVar('PODMAN_OCI_RUNTIME') or "").strip()
    if not registry and not oci_runtime:
        raise bb.parse.SkipRecipe("No registry or OCI runtime configured - recipe is opt-in only")

    # Check for conflicting settings
    secure = d.getVar('CONTAINER_REGISTRY_SECURE') == '1'
    insecure = d.getVar('CONTAINER_REGISTRY_INSECURE') == '1'

    if secure and insecure:
        bb.fatal("CONTAINER_REGISTRY_SECURE='1' and CONTAINER_REGISTRY_INSECURE='1' cannot both be set. "
                 "Use secure mode (TLS+auth) OR insecure mode (HTTP), not both.")

    # In secure mode, depend on PKI generation
    if secure:
        d.appendVarFlag('do_install', 'depends', ' container-registry-index:do_generate_registry_script')
}

python do_install() {
    import os
    import shutil

    registry = d.getVar('CONTAINER_REGISTRY_URL')
    insecure = d.getVar('CONTAINER_REGISTRY_INSECURE') == "1"
    secure = d.getVar('CONTAINER_REGISTRY_SECURE') == "1"
    search_first = d.getVar('CONTAINER_REGISTRY_SEARCH_FIRST') == "1"
    ca_cert = d.getVar('CONTAINER_REGISTRY_CA_CERT')
    authfile = d.getVar('CONTAINER_REGISTRY_AUTHFILE') or ''
    oci_runtime = (d.getVar('PODMAN_OCI_RUNTIME') or "").strip()
    oci_runtime_path = (d.getVar('PODMAN_OCI_RUNTIME_PATH') or "").strip()

    dest = d.getVar('D')

    # --- Registry configuration ---
    if registry:
        # Extract registry host (strip any path)
        registry_host = registry.split('/')[0] if '/' in registry else registry

        confdir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'),
                               'containers', 'registries.conf.d')
        os.makedirs(confdir, exist_ok=True)

        # Install CA cert in secure mode
        if secure:
            if os.path.exists(ca_cert):
                cert_dir = os.path.join(dest, 'etc/containers/certs.d', registry_host)
                os.makedirs(cert_dir, exist_ok=True)
                shutil.copy(ca_cert, os.path.join(cert_dir, 'ca.crt'))
                bb.note("Installed CA certificate for registry: %s" % registry_host)
            else:
                bb.warn("Secure mode enabled but CA certificate not found at %s" % ca_cert)
                bb.warn("Run 'container-registry.sh start' to generate PKI, then rebuild this package")

        # In secure mode, insecure should be false
        if secure:
            insecure = False

        # Generate drop-in config
        config_path = os.path.join(confdir, '50-custom-registry.conf')

        with open(config_path, 'w') as f:
            f.write("# Custom container registry: %s\n" % registry)
            f.write("# Generated by container-oci-registry-config recipe\n")
            f.write("# This is ADDITIVE - base registries.conf is unchanged\n")
            f.write("# Public registries (docker.io, quay.io) remain accessible\n")
            f.write("#\n")
            if secure:
                f.write("# Mode: secure (TLS with CA certificate verification)\n")
                f.write("# CA cert: /etc/containers/certs.d/%s/ca.crt\n" % registry_host)
            else:
                f.write("# Mode: insecure (HTTP or untrusted TLS)\n")
            f.write("#\n")
            f.write("# To remove: uninstall container-oci-registry-config package\n")
            f.write("# or delete this file\n\n")

            if search_first:
                f.write("# Search this registry for unqualified image names\n")
                f.write('unqualified-search-registries = ["%s"]\n\n' % registry)

            f.write('[[registry]]\n')
            f.write('location = "%s"\n' % registry_host)
            if insecure:
                f.write('insecure = true\n')
            else:
                f.write('insecure = false\n')

        mode = "secure" if secure else ("insecure" if insecure else "default")
        bb.note("Created registry config for %s (mode=%s)" % (registry, mode))

    # --- OCI runtime configuration ---
    if oci_runtime:
        dropin_dir = os.path.join(dest, d.getVar('sysconfdir').lstrip('/'),
                                  'containers', 'containers.conf.d')
        os.makedirs(dropin_dir, exist_ok=True)

        runtime_path = oci_runtime_path if oci_runtime_path else "/usr/bin/%s" % oci_runtime
        dropin_path = os.path.join(dropin_dir, '50-oci-runtime.conf')

        with open(dropin_path, 'w') as f:
            f.write("# OCI runtime configuration\n")
            f.write("# Generated by container-oci-registry-config recipe\n\n")
            f.write("[engine]\n")
            f.write('runtime = "%s"\n\n' % oci_runtime)
            f.write("[engine.runtimes]\n")
            f.write('%s = ["%s"]\n' % (oci_runtime, runtime_path))

        bb.note("Created OCI runtime drop-in for %s (%s)" % (oci_runtime, runtime_path))

    # Install authfile if provided (for baked credentials)
    if authfile and os.path.exists(authfile):
        containers_dir = os.path.join(dest, 'etc/containers')
        os.makedirs(containers_dir, exist_ok=True)
        auth_json = os.path.join(containers_dir, 'auth.json')
        shutil.copy(authfile, auth_json)
        os.chmod(auth_json, 0o600)
        bb.note("Installed OCI auth config from %s" % authfile)
}

FILES:${PN} = " \
    ${sysconfdir}/containers/registries.conf.d \
    ${sysconfdir}/containers/containers.conf.d \
    ${sysconfdir}/containers/certs.d/*/ca.crt \
    ${sysconfdir}/containers/auth.json \
"

# Ensure proper permissions on auth file
pkg_postinst:${PN}() {
#!/bin/sh
if [ -f $D/etc/containers/auth.json ]; then
    chmod 600 $D/etc/containers/auth.json
fi
}

# Soft dependency - works with or without container-host-config
# If container-host-config is installed, our drop-in extends it
RRECOMMENDS:${PN} = "container-host-config"