Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions pkg/minikube/bootstrapper/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
40 changes: 38 additions & 2 deletions pkg/minikube/node/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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"
}
Expand Down