diff --git a/pkg/minikube/bootstrapper/certs.go b/pkg/minikube/bootstrapper/certs.go index 2dc7b987072e..aa09e58ca6d8 100644 --- a/pkg/minikube/bootstrapper/certs.go +++ b/pkg/minikube/bootstrapper/certs.go @@ -638,6 +638,55 @@ func isKubeadmCertValid(cmd command.Runner, certPath string) bool { return err == nil } +// EnsureCACertsEarly collects host-provided custom CA certs, copies them into the guest, +// installs symlinks into the system trust store, and refreshes trust *before* HTTPS probes. +func EnsureCACertsEarly(cr command.Runner) error { + caCerts, err := collectCACerts() + if err != nil { + return err + } + if len(caCerts) == 0 { + // Nothing to do. + return nil + } + + // 1) Copy CA files from host → guest at the intended paths (e.g. /usr/share/ca-certificates/*.pem) + // This mirrors how SetupCerts does file transfer for other certs. + copyable := []assets.CopyableFile{} + defer func() { + for _, f := range copyable { + _ = f.Close() + } + }() + for src, dst := range caCerts { + dir := path.Dir(dst) // NOTE: use "path" (guest path), not "filepath" + base := path.Base(dst) + f, err := assets.NewFileAsset(src, dir, base, "0644") + if err != nil { + return errors.Wrapf(err, "create ca cert file asset for %s", src) + } + copyable = append(copyable, f) + } + for _, f := range copyable { + if err := cr.Copy(f); err != nil { + return errors.Wrapf(err, "copy %s", f.GetSourcePath()) + } + } + + // 2) Create/store symlinks + subject-hash links in /etc/ssl/certs (reuses existing helper) + if err := installCertSymlinks(cr, caCerts); err != nil { + return err + } + + // 3) Refresh trust store for common distros; ignore if tools are absent. + _, _ = cr.RunCmd(exec.Command("/bin/sh", "-c", + "command -v update-ca-certificates >/dev/null 2>&1 && sudo update-ca-certificates || true")) + _, _ = cr.RunCmd(exec.Command("/bin/sh", "-c", + "command -v update-ca-trust >/dev/null 2>&1 && sudo update-ca-trust extract || true")) + + return nil +} + // properPerms returns proper permissions for given cert file, based on its extension. func properPerms(cert string) string { perms := "0644" diff --git a/pkg/minikube/node/start.go b/pkg/minikube/node/start.go index f62799853e7d..e493abd6c36d 100755 --- a/pkg/minikube/node/start.go +++ b/pkg/minikube/node/start.go @@ -782,6 +782,13 @@ func validateNetwork(h *host.Host, r command.Runner, imageRepository string) (st } } + // Ensure host-provided custom CAs are installed/trusted *before* we do HTTPS connectivity checks. + // This prevents a misleading "SSL certificate problem" warning for corp proxies/VPNs. + if err := bootstrapper.EnsureCACertsEarly(r); err != nil { + // Non-fatal; continue with the probe anyway. + klog.Warningf("pre-probe CA setup failed: %v", err) + } + if shouldTrySSH(h.Driver.DriverName(), ip) { if err := trySSH(h, ip); err != nil { return ip, err @@ -844,6 +851,19 @@ func trySSH(h *host.Host, ip string) error { return err } +// isCertError reports whether an error from the in-guest probe looks like a trust problem. +// We keep this scoped to common curl/OpenSSL messages to avoid masking real connectivity issues. +func isCertError(err error) bool { + if err == nil { + return false + } + s := err.Error() + return strings.Contains(s, "curl: (60)") || + strings.Contains(s, "SSL certificate problem") || + strings.Contains(s, "certificate signed by unknown authority") || + strings.Contains(s, "x509: certificate signed by unknown authority") +} + // tryRegistry tries to connect to the image repository func tryRegistry(r command.Runner, driverName, imageRepository, ip string) { // 2 second timeout. For best results, call tryRegistry in a non-blocking manner. @@ -864,8 +884,22 @@ func tryRegistry(r command.Runner, driverName, imageRepository, ip string) { if runtime.GOOS == "windows" { exe = "curl.exe" } + // First in-guest probe cmd := exec.Command(exe, opts...) - if rr, err := r.RunCmd(cmd); err != nil { + rr, err := r.RunCmd(cmd) + if err != nil { + // Retry once if the first failure looks like a cert trust error. + if isCertError(err) { + cmdRetry := exec.Command(exe, opts...) + if rr2, err2 := r.RunCmd(cmdRetry); err2 == nil { + // Success after CA trust; suppress the earlier warning entirely. + return + } else { + // Use the latest error/output for logging below. + rr, err = rr2, err2 + } + } + klog.Warningf("%s failed: %v", rr.Args, err) // using QEMU with the user network @@ -883,7 +917,9 @@ func tryRegistry(r command.Runner, driverName, imageRepository, ip string) { // on the ssh driver is not helpful. warning := "Failing to connect to {{.curlTarget}} from inside the minikube {{.type}}" if !driver.IsNone(driverName) && !driver.IsSSH(driverName) { - if err := cmd.Run(); err != nil { + // Build a fresh host-side command; exec.Cmd cannot be reused after Run(). + cmdHost := exec.Command(exe, opts...) + if err := cmdHost.Run(); err != nil { // both inside and outside failed warning = "Failing to connect to {{.curlTarget}} from both inside the minikube {{.type}} and host machine" }