|
| 1 | +import dataclasses |
| 2 | +import json |
1 | 3 | import os
|
2 | 4 | import pathlib
|
3 | 5 | import platform
|
@@ -147,3 +149,225 @@ def create_filesystem_customizations(rootfs: str):
|
147 | 149 | "-v", "/var/lib/containers/storage:/var/lib/containers/storage",
|
148 | 150 | "--security-opt", "label=type:unconfined_t",
|
149 | 151 | ]
|
| 152 | + |
| 153 | + |
| 154 | +def get_ip_from_default_route(): |
| 155 | + default_route = subprocess.run([ |
| 156 | + "ip", |
| 157 | + "route", |
| 158 | + "list", |
| 159 | + "default" |
| 160 | + ], check=True, capture_output=True, text=True).stdout |
| 161 | + return default_route.split()[8] |
| 162 | + |
| 163 | + |
| 164 | +@dataclasses.dataclass |
| 165 | +class GPGConf: |
| 166 | + _key_params_tmpl: str = """ |
| 167 | + %no-protection |
| 168 | + Key-Type: RSA |
| 169 | + Key-Length: {key_length} |
| 170 | + Key-Usage: sign |
| 171 | + Name-Real: Bootc Image Builder Tests |
| 172 | + Name-Email: {email} |
| 173 | + Expire-Date: 0 |
| 174 | + """ |
| 175 | + _base_dir: str = "/tmp/bib-tests" |
| 176 | + |
| 177 | + _key_length: str = "3072" |
| 178 | + home_dir: str = f"{_base_dir}/.gnupg" |
| 179 | + pub_key_file: str = f"{_base_dir}/GPG-KEY-bib-tests" |
| 180 | + key_params: str = _key_params_tmpl.format(key_length=_key_length, email=_email) |
| 181 | + |
| 182 | + @property |
| 183 | + def base_dir(self) -> str: |
| 184 | + return self._base_dir |
| 185 | + |
| 186 | + @base_dir.setter |
| 187 | + def base_dir(self, base_dir: str) -> None: |
| 188 | + self._base_dir = base_dir |
| 189 | + self.home_dir = f"{base_dir}/.gnupg" |
| 190 | + self.pub_key_file = f"{base_dir}/GPG-KEY-bib-tests" |
| 191 | + |
| 192 | + @property |
| 193 | + def email(self) -> str: |
| 194 | + return self._email |
| 195 | + |
| 196 | + @email.setter |
| 197 | + def email(self, email: str) -> None: |
| 198 | + self._email = email |
| 199 | + self.key_params = self._key_params_tmpl.format( |
| 200 | + email=self._email, |
| 201 | + key_length=self._key_length |
| 202 | + ) |
| 203 | + |
| 204 | + @property |
| 205 | + def key_length(self) -> str: |
| 206 | + return self._key_length |
| 207 | + |
| 208 | + @key_length.setter |
| 209 | + def key_length(self, length: str) -> None: |
| 210 | + self._key_length = length |
| 211 | + self._key_params = self._key_params_tmpl.format( |
| 212 | + email=self._email, |
| 213 | + key_length=self._key_length |
| 214 | + ) |
| 215 | + |
| 216 | +def gpg_gen_key(gpg_conf: GPGConf): |
| 217 | + if os.path.exists(gpg_conf.home_dir): |
| 218 | + return |
| 219 | + |
| 220 | + os.makedirs(gpg_conf.home_dir, mode=0o700, exist_ok=False) |
| 221 | + |
| 222 | + subprocess.run( |
| 223 | + ["gpg", "--gen-key", "--batch"], |
| 224 | + check=True, env={"GNUPGHOME": gpg_conf.home_dir}, |
| 225 | + input=gpg_conf.key_params, |
| 226 | + text=True) |
| 227 | + |
| 228 | + subprocess.run( |
| 229 | + ["gpg", "--output", gpg_conf.pub_key_file, |
| 230 | + "--armor", "--export", gpg_conf.email], |
| 231 | + check=True, env={"GNUPGHOME": gpg_conf.home_dir}) |
| 232 | + |
| 233 | + |
| 234 | +@dataclasses.dataclass |
| 235 | +class RegistryConf(): |
| 236 | + _lookaside_conf_tmpl: str = """ |
| 237 | + docker: |
| 238 | + {local_registry}: |
| 239 | + lookaside: file:///{sigstore_dir} |
| 240 | + """ |
| 241 | + _local_registry: str = "localhost:5000" |
| 242 | + _base_dir: str = "/tmp/bib-tests" |
| 243 | + _sigstore_dir: str = f"{_base_dir}/sigstore" |
| 244 | + _registries_d_dir: str = f"{_base_dir}/registries.d" |
| 245 | + policy_file: str = f"{_base_dir}/policy.json" |
| 246 | + lookaside_conf_file: str = f"{_registries_d_dir}/lookaside.yml" |
| 247 | + lookaside_conf: str = _lookaside_conf_tmpl.format( |
| 248 | + local_registry=_local_registry, |
| 249 | + sigstore_dir=_sigstore_dir |
| 250 | + ) |
| 251 | + |
| 252 | + @property |
| 253 | + def local_registry(self) -> str: |
| 254 | + return self._local_registry |
| 255 | + |
| 256 | + @local_registry.setter |
| 257 | + def local_registry(self, registry: str) -> None: |
| 258 | + self._local_registry = registry |
| 259 | + self.lookaside_conf = self._lookaside_conf_tmpl.format( |
| 260 | + local_registry=self._local_registry, |
| 261 | + sigstore_dir=self._sigstore_dir |
| 262 | + ) |
| 263 | + |
| 264 | + @property |
| 265 | + def base_dir(self) -> str: |
| 266 | + return self._base_dir |
| 267 | + |
| 268 | + @base_dir.setter |
| 269 | + def base_dir(self, base_dir: str) -> None: |
| 270 | + self._base_dir = base_dir |
| 271 | + self._sigstore_dir = f"{base_dir}/sigstore" |
| 272 | + self.policy_file = f"{base_dir}/policy.json" |
| 273 | + self._registries_d_dir = f"{base_dir}/registries.d" |
| 274 | + self.lookaside_conf_file = f"{self._registries_d_dir}/lookaside.yaml" |
| 275 | + self.lookaside_conf = self._lookaside_conf_tmpl.format( |
| 276 | + local_registry=self._local_registry, |
| 277 | + sigstore_dir=self._sigstore_dir |
| 278 | + ) |
| 279 | + |
| 280 | + @property |
| 281 | + def sigstore_dir(self) -> str: |
| 282 | + return self._sigstore_dir |
| 283 | + |
| 284 | + @sigstore_dir.setter |
| 285 | + def sigstore_dir(self, sigstore_dir: str) -> None: |
| 286 | + self._sigstore_dir = sigstore_dir |
| 287 | + self.lookaside_conf = self._lookaside_conf_tmpl.format( |
| 288 | + local_registry=self._local_registry, |
| 289 | + sigstore_dir=self._sigstore_dir |
| 290 | + ) |
| 291 | + |
| 292 | + @property |
| 293 | + def registries_d_dir(self) -> str: |
| 294 | + return self._registries_d_dir |
| 295 | + |
| 296 | + @registries_d_dir.setter |
| 297 | + def registries_d_dir(self, dir: str) -> None: |
| 298 | + self._registries_d_dir= dir |
| 299 | + self.lookaside_conf_file = f"{self._registries_d_dir}/lookaside.yaml" |
| 300 | + |
| 301 | +def ensure_registry(registry_conf: RegistryConf): |
| 302 | + registry_container_name = subprocess.run([ |
| 303 | + "podman", "ps", "-a", "--filter", "name=registry", "--format", "{{.Names}}" |
| 304 | + ], check=True, capture_output=True, text=True).stdout.strip() |
| 305 | + |
| 306 | + registry_port = registry_conf.local_registry.split(":")[1] |
| 307 | + if registry_container_name != "registry": |
| 308 | + subprocess.run([ |
| 309 | + "podman", "run", "-d", |
| 310 | + "-p", f"{registry_port}:5000", |
| 311 | + "--restart", "always", |
| 312 | + "--name", "registry", |
| 313 | + "registry:2" |
| 314 | + ], check=True) |
| 315 | + |
| 316 | + registry_container_state = subprocess.run([ |
| 317 | + "podman", "ps", "-a", "--filter", "name=registry", "--format", "{{.State}}" |
| 318 | + ], check=True, capture_output=True, text=True).stdout.strip() |
| 319 | + |
| 320 | + if registry_container_state in ("paused", "exited"): |
| 321 | + subprocess.run([ |
| 322 | + "podman", "start", "registry" |
| 323 | + ], check=True) |
| 324 | + |
| 325 | + |
| 326 | +def get_signed_container_ref(local_registry: str, container_ref: str): |
| 327 | + container_ref_path = container_ref[container_ref.index('/'):] |
| 328 | + return f"{local_registry}{container_ref_path}" |
| 329 | + |
| 330 | + |
| 331 | +def sign_container_image(gpg_conf: GPGConf, registry_conf: RegistryConf, container_ref): |
| 332 | + gpg_gen_key(gpg_conf) |
| 333 | + ensure_registry(registry_conf) |
| 334 | + local_registry = registry_conf.local_registry |
| 335 | + policy_file = registry_conf.policy_file |
| 336 | + registries_d_dir = registry_conf.registries_d_dir |
| 337 | + lookaside_conf_file = registry_conf.lookaside_conf_file |
| 338 | + lookaside_conf = registry_conf.lookaside_conf |
| 339 | + pub_key_file = gpg_conf.pub_key_file |
| 340 | + home_dir = gpg_conf.home_dir |
| 341 | + email = gpg_conf.email |
| 342 | + registry_policy = { |
| 343 | + "default": [{"type": "insecureAcceptAnything"}], |
| 344 | + "transports": { |
| 345 | + "docker": { |
| 346 | + f"{local_registry}": [ |
| 347 | + { |
| 348 | + "type": "signedBy", |
| 349 | + "keyType": "GPGKeys", |
| 350 | + "keyPath": f"{pub_key_file}" |
| 351 | + } |
| 352 | + ] |
| 353 | + }, |
| 354 | + "docker-daemon": { |
| 355 | + "": [{"type": "insecureAcceptAnything"}] |
| 356 | + } |
| 357 | + } |
| 358 | + } |
| 359 | + with open(policy_file, mode="w", encoding="utf-8") as f: |
| 360 | + f.write(json.dumps(registry_policy)) |
| 361 | + |
| 362 | + os.makedirs(os.path.dirname(lookaside_conf_file), mode=0o700, exist_ok=False) |
| 363 | + with open(lookaside_conf_file, mode="w", encoding="utf-8") as f: |
| 364 | + f.write(lookaside_conf) |
| 365 | + |
| 366 | + signed_container_ref = get_signed_container_ref(local_registry, container_ref) |
| 367 | + subprocess.run([ |
| 368 | + "skopeo", "--registries.d", f"{registries_d_dir}", |
| 369 | + "copy", "--dest-tls-verify=false", "--remove-signatures", |
| 370 | + "--sign-by", email, |
| 371 | + f"docker://{container_ref}", |
| 372 | + f"docker://{signed_container_ref}", |
| 373 | + ], check=True, env={"GNUPGHOME": home_dir}) |
0 commit comments