]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add proxy settings
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 21 Mar 2024 10:32:27 +0000 (11:32 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 21 Mar 2024 13:13:03 +0000 (14:13 +0100)
These allow using mkosi behind a proxy that requires proxy authentication.
Only dnf seems to allow specifying these certificates as individual settings
so other package managers are not fully supported for now.

We mount the proxy certificates and keys to /proxy.xxx in the sandbox because
otherwise they might end up being mounted at the same location as the certificates
from the tools tree, which means those wouldn't be used.

mkosi/__init__.py
mkosi/config.py
mkosi/distributions/opensuse.py
mkosi/installer/apt.py
mkosi/installer/dnf.py
mkosi/resources/mkosi.md
tests/test_json.py

index ef06b859179c7a4a230ce592625413909ecd4e51..bd9017600f52857b5bbdc42a4c87294a1c2cc8f3 100644 (file)
@@ -1665,6 +1665,10 @@ def finalize_default_initrd(
         *([f"--environment={k}='{v}'" for k, v in config.environment.items()]),
         *(["--tools-tree", str(config.tools_tree)] if config.tools_tree else []),
         *([f"--extra-search-path={p}" for p in config.extra_search_paths]),
+        *(["--proxy-url", config.proxy_url] if config.proxy_url else []),
+        *(["--proxy-peer-certificate", str(p)] if (p := config.proxy_peer_certificate) else []),
+        *(["--proxy-client-certificate", str(p)] if (p := config.proxy_client_certificate) else []),
+        *(["--proxy-client-key", str(p)] if (p := config.proxy_client_key) else []),
         *(["-f"] * args.force),
     ]
 
@@ -3932,6 +3936,10 @@ def finalize_default_tools(args: Args, config: Config, *, resources: Path) -> Co
         *(["--source-date-epoch", str(config.source_date_epoch)] if config.source_date_epoch is not None else []),
         *([f"--environment={k}='{v}'" for k, v in config.environment.items()]),
         *([f"--extra-search-path={p}" for p in config.extra_search_paths]),
+        *(["--proxy-url", config.proxy_url] if config.proxy_url else []),
+        *(["--proxy-peer-certificate", str(p)] if (p := config.proxy_peer_certificate) else []),
+        *(["--proxy-client-certificate", str(p)] if (p := config.proxy_client_certificate) else []),
+        *(["--proxy-client-key", str(p)] if (p := config.proxy_client_key) else []),
         *(["-f"] * args.force),
     ]
 
index d100dd34657865c95f4867d3bbfb67b3eee37cbf..c2e6e2a4d64e9b6a1cca47180aec438a67d9bb05 100644 (file)
@@ -677,6 +677,21 @@ def config_default_source_date_epoch(namespace: argparse.Namespace) -> Optional[
     return config_parse_source_date_epoch(s, None)
 
 
+def config_default_proxy_url(namespace: argparse.Namespace) -> Optional[str]:
+    names = ("http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY")
+
+    for env in namespace.environment:
+        k, _, v = env.partition("=")
+        if k in names:
+            return cast(str, v)
+
+    for k, v in os.environ.items():
+        if k in names:
+            return cast(str, v)
+
+    return None
+
+
 def config_default_kernel_command_line(namespace: argparse.Namespace) -> list[str]:
     return [f"console={namespace.architecture.default_serial_tty()}"]
 
@@ -1366,6 +1381,10 @@ class Config:
     sign: bool
     key: Optional[str]
 
+    proxy_url: Optional[str]
+    proxy_peer_certificate: Optional[Path]
+    proxy_client_certificate: Optional[Path]
+    proxy_client_key: Optional[Path]
     incremental: bool
     nspawn_settings: Optional[Path]
     extra_search_paths: list[Path]
@@ -1572,6 +1591,9 @@ class Config:
     ) -> list[PathString]:
         mounts = [
             *[Mount(d, d, ro=True) for d in self.extra_search_paths if not relaxed and not self.tools_tree],
+            *([Mount(p, "/proxy.cacert", ro=True)] if (p := self.proxy_peer_certificate) else []),
+            *([Mount(p, "/proxy.clientcert", ro=True)] if (p := self.proxy_client_certificate) else []),
+            *([Mount(p, "/proxy.clientkey", ro=True)] if (p := self.proxy_client_key) else []),
             *mounts,
         ]
 
@@ -2477,6 +2499,38 @@ SETTINGS = (
         help="GPG key to use for signing",
     ),
 
+    ConfigSetting(
+        dest="proxy_url",
+        section="Host",
+        default_factory=config_default_proxy_url,
+        default_factory_depends=("environment",),
+        metavar="URL",
+        help="Set the proxy to use",
+    ),
+    ConfigSetting(
+        dest="proxy_peer_certificate",
+        section="Host",
+        parse=config_make_path_parser(),
+        paths=(
+            "/etc/pki/tls/certs/ca-bundle.crt",
+            "/etc/ssl/certs/ca-certificates.crt",
+        ),
+        help="Set the proxy peer certificate",
+    ),
+    ConfigSetting(
+        dest="proxy_client_certificate",
+        section="Host",
+        parse=config_make_path_parser(secret=True),
+        help="Set the proxy client certificate",
+    ),
+    ConfigSetting(
+        dest="proxy_client_key",
+        section="Host",
+        default_factory=lambda ns: ns.proxy_client_certificate,
+        default_factory_depends=("proxy_client_certificate",),
+        parse=config_make_path_parser(secret=True),
+        help="Set the proxy client key",
+    ),
     ConfigSetting(
         dest="incremental",
         short="-i",
@@ -3493,10 +3547,16 @@ def load_environment(args: argparse.Namespace) -> dict[str, str]:
         env["IMAGE_VERSION"] = args.image_version
     if args.source_date_epoch is not None:
         env["SOURCE_DATE_EPOCH"] = str(args.source_date_epoch)
-    if proxy := os.getenv("http_proxy"):
-        env["http_proxy"] = proxy
-    if proxy := os.getenv("https_proxy"):
-        env["https_proxy"] = proxy
+    if args.proxy_url is not None:
+        for e in ("http_proxy", "https_proxy"):
+            env[e] = args.proxy_url
+            env[e.upper()] = args.proxy_url
+    if args.proxy_peer_certificate:
+        env["GIT_PROXY_SSL_CAINFO"] = "/proxy.cacert"
+    if args.proxy_client_certificate:
+        env["GIT_PROXY_SSL_CERT"] = "/proxy.clientcert"
+    if args.proxy_client_key:
+        env["GIT_PROXY_SSL_KEY"] = "/proxy.clientkey"
     if dnf := os.getenv("MKOSI_DNF"):
         env["MKOSI_DNF"] = dnf
 
@@ -3737,6 +3797,10 @@ def summary(config: Config) -> str:
     summary += f"""\
 
     {bold("HOST CONFIGURATION")}:
+                          Proxy URL: {none_to_none(config.proxy_url)}
+             Proxy Peer Certificate: {none_to_none(config.proxy_peer_certificate)}
+           Proxy Client Certificate: {none_to_none(config.proxy_client_certificate)}
+                   Proxy Client Key: {none_to_none(config.proxy_client_key)}
                         Incremental: {yes_no(config.incremental)}
                     NSpawn Settings: {none_to_none(config.nspawn_settings)}
                  Extra Search Paths: {line_join_list(config.extra_search_paths)}
index 8451e22308239a1dd9a16fd1c40bb35907527328..19dc7e9306ee9348cfc9da2bcd993c83690b0003 100644 (file)
@@ -164,6 +164,10 @@ def fetch_gpgurls(context: Context, repourl: str) -> tuple[str, ...]:
                 "--remote-name",
                 "--no-progress-meter",
                 "--fail",
+                *(["--proxy", context.config.proxy_url] if context.config.proxy_url else []),
+                *(["--proxy-capath", "/proxy.cacert"] if context.config.proxy_peer_certificate else []),
+                *(["--proxy-cert", "/proxy.clientcert"] if context.config.proxy_client_certificate else []),
+                *(["--proxy-key", "/proxy.clientkey"] if context.config.proxy_client_key else []),
                 f"{repourl}/repodata/repomd.xml",
             ],
             sandbox=context.sandbox(
index c8c007857774d0318e45bb0057d7b2db724a0257..8f9c1a8a9a1ae4f4d7896c8a5ef0203f0a5c70f9 100644 (file)
@@ -172,6 +172,12 @@ class Apt(PackageManager):
                 "-o", "DPkg::Options::=--path-exclude=/usr/share/info/*",
             ]
 
+        if context.config.proxy_url:
+            cmdline += [
+                "-o", f"Acquire::http::Proxy={context.config.proxy_url}",
+                "-o", f"Acquire::https::Proxy={context.config.proxy_url}",
+            ]
+
         return cmdline
 
     @classmethod
index 0c17e8776393c08d97f9fcbbe2d5b5a7bab2ba7c..1544722f357d1854975efb0bd798388b6a954a90 100644 (file)
@@ -148,6 +148,15 @@ class Dnf(PackageManager):
                 "--setopt=varsdir=/etc/dnf/vars",
             ]
 
+        if context.config.proxy_url:
+            cmdline += [f"--setopt=proxy={context.config.proxy_url}"]
+        if context.config.proxy_peer_certificate:
+            cmdline += ["--setopt=proxy_sslcacert=/proxy.cacert"]
+        if context.config.proxy_client_certificate:
+            cmdline += ["--setopt=proxy_sslclientcert=/proxy.clientcert"]
+        if context.config.proxy_client_key:
+            cmdline += ["--setopt=proxy_sslclientkey=/proxy.clientkey"]
+
         return cmdline
 
     @classmethod
index 0e5217c65e6577dcc7d0234b0c74f652990cbb7a..d16611f8b9cc4e023b8d3588388e567921a07e5b 100644 (file)
@@ -1417,6 +1417,34 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 ### [Host] Section
 
+`ProxyUrl=`, `--proxy-url=`
+
+: Configure a proxy to be used for all outgoing network connections.
+  Various tools that mkosi invokes and for which the proxy can be
+  configured are configured to use this proxy. mkosi also sets various
+  well-known environment variables to specify the proxy to use for any
+  programs it invokes that may need internet access.
+
+`ProxyPeerCertificate=`, `--proxy-peer-certificate=`
+
+: Configure a file containing certificates used to verify the proxy.
+  Defaults to the system-wide certificate store. Note that not all
+  package managers that mkosi uses have support for specifying a proxy
+  peer certificate.
+
+`ProxyClientCertificate=`, `--proxy-client-certificate=`
+
+: Configure a file containing the certificate used to authenticate the
+  client with the proxy. Note that not all package managers that mkosi
+  uses have support for specifying a proxy client certificate.
+
+`ProxyClientKey=`, `--proxy-client-key=`
+
+: Configure a file containing the private key used to authenticate the
+  client with the proxy. Defaults to the proxy client certificate if one
+  is provided. Note that not all package managers that mkosi uses have
+  support for specifying a proxy client key.
+
 `Incremental=`, `--incremental=`, `-i`
 
 : Enable incremental build mode. In this mode, a copy of the OS image is
index 0b0859c21c54c0169b95acd6e99537689140eef1..3cd112935a1cb9bdfe6d546cfe1a54450b87cf0b 100644 (file)
@@ -203,6 +203,10 @@ def test_config() -> None:
                 "/run/foo"
             ],
             "Profile": "profile",
+            "ProxyClientCertificate": "/my/client/cert",
+            "ProxyClientKey": "/my/client/key",
+            "ProxyPeerCertificate": "/my/peer/cert",
+            "ProxyUrl": "https://my/proxy",
             "QemuArgs": [],
             "QemuCdrom": false,
             "QemuDrives": [
@@ -393,6 +397,10 @@ def test_config() -> None:
         postinst_scripts = [Path("/bar/qux")],
         prepare_scripts = [Path("/run/foo")],
         profile = "profile",
+        proxy_client_certificate = Path("/my/client/cert"),
+        proxy_client_key = Path("/my/client/key"),
+        proxy_peer_certificate = Path("/my/peer/cert"),
+        proxy_url = "https://my/proxy",
         qemu_args = [],
         qemu_cdrom = False,
         qemu_drives = [QemuDrive("abc", 200, Path("/foo/bar"), "abc,qed"), QemuDrive("abc", 200, None, "")],