Skip to content

Commit ce455ab

Browse files
committed
pkg/hostagent: Disable ControlMaster options on executing SSH until the 2nd essential requirement is satisfied
debugHint in 2nd essential requirement says: > The boot sequence will terminate any existing user session after updating > /etc/environment to make sure the session includes the new values. > Terminating the session will break the persistent SSH tunnel, so > it must not be created until the session reset is done. This explicitly disables `ControlMaster` options when executing SSH until the second essential requirement is satisfied. Also, if there are two essential requirements, a requirement to explicitly enable ControlMaster will be added. Signed-off-by: Norio Nomura <[email protected]>
1 parent 9d815d5 commit ce455ab

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

pkg/hostagent/requirements.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/sirupsen/logrus"
1414

1515
"github.com/lima-vm/lima/v2/pkg/limatype"
16+
"github.com/lima-vm/lima/v2/pkg/sshutil"
1617
)
1718

1819
func (a *HostAgent) waitForRequirements(label string, requirements []requirement) error {
@@ -101,7 +102,15 @@ func (a *HostAgent) waitForRequirement(r requirement) error {
101102
if err != nil {
102103
return err
103104
}
104-
stdout, stderr, err := ssh.ExecuteScript(a.instSSHAddress, a.sshLocalPort, a.sshConfig, script, r.description)
105+
sshConfig := a.sshConfig
106+
if r.noMaster {
107+
sshConfig = &ssh.SSHConfig{
108+
ConfigFile: sshConfig.ConfigFile,
109+
Persist: false,
110+
AdditionalArgs: sshutil.DisableControlMasterOptsFromSSHArgs(sshConfig.AdditionalArgs),
111+
}
112+
}
113+
stdout, stderr, err := ssh.ExecuteScript(a.instSSHAddress, a.sshLocalPort, sshConfig, script, r.description)
105114
logrus.Debugf("stdout=%q, stderr=%q, err=%v", stdout, stderr, err)
106115
if err != nil {
107116
return fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err)
@@ -114,6 +123,7 @@ type requirement struct {
114123
script string
115124
debugHint string
116125
fatal bool
126+
noMaster bool
117127
}
118128

119129
func (a *HostAgent) essentialRequirements() []requirement {
@@ -128,9 +138,17 @@ true
128138
Make sure that the YAML field "ssh.localPort" is not used by other processes on the host.
129139
If any private key under ~/.ssh is protected with a passphrase, you need to have ssh-agent to be running.
130140
`,
141+
noMaster: true,
131142
})
143+
startControlMasterReq := requirement{
144+
description: "Explicitly start ssh ControlMaster",
145+
script: `#!/bin/bash
146+
true
147+
`,
148+
debugHint: `The persistent ssh ControlMaster should be started immediately.`,
149+
}
132150
if *a.instConfig.Plain {
133-
return req
151+
return append(req, startControlMasterReq)
134152
}
135153
req = append(req,
136154
requirement{
@@ -147,6 +165,7 @@ fi
147165
Terminating the session will break the persistent SSH tunnel, so
148166
it must not be created until the session reset is done.
149167
`,
168+
noMaster: true,
150169
})
151170

152171
if *a.instConfig.MountType == limatype.REVSSHFS && len(a.instConfig.Mounts) > 0 {
@@ -176,6 +195,8 @@ fi
176195
`,
177196
debugHint: `Append "user_allow_other" to /etc/fuse.conf (/etc/fuse3.conf) in the guest`,
178197
})
198+
} else {
199+
req = append(req, startControlMasterReq)
179200
}
180201
return req
181202
}

pkg/sshutil/sshutil.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"path/filepath"
1717
"regexp"
1818
"runtime"
19+
"slices"
1920
"strings"
2021
"sync"
2122
"time"
@@ -294,6 +295,36 @@ func identityFileEntry(ctx context.Context, privateKeyPath string) (string, erro
294295
return fmt.Sprintf(`IdentityFile="%s"`, privateKeyPath), nil
295296
}
296297

298+
// DisableControlMasterOptsFromSSHArgs returns ssh args that disable ControlMaster, ControlPath, and ControlPersist.
299+
func DisableControlMasterOptsFromSSHArgs(sshArgs []string) []string {
300+
argsForOverridingConfigFile := []string{
301+
"-o", "ControlMaster=no",
302+
"-o", "ControlPath=none",
303+
"-o", "ControlPersist=no",
304+
}
305+
return append(argsForOverridingConfigFile, removeOptsFromSSHArgs(sshArgs, "ControlMaster", "ControlPath", "ControlPersist")...)
306+
}
307+
308+
func removeOptsFromSSHArgs(sshArgs []string, removeOpts ...string) []string {
309+
res := make([]string, 0, len(sshArgs))
310+
isOpt := false
311+
for _, arg := range sshArgs {
312+
if isOpt {
313+
isOpt = false
314+
if !slices.ContainsFunc(removeOpts, func(opt string) bool {
315+
return strings.HasPrefix(arg, opt)
316+
}) {
317+
res = append(res, "-o", arg)
318+
}
319+
} else if arg == "-o" {
320+
isOpt = true
321+
} else {
322+
res = append(res, arg)
323+
}
324+
}
325+
return res
326+
}
327+
297328
// SSHOpts adds the following options to CommonOptions: User, ControlMaster, ControlPath, ControlPersist.
298329
func SSHOpts(ctx context.Context, sshExe SSHExe, instDir, username string, useDotSSH, forwardAgent, forwardX11, forwardX11Trusted bool) ([]string, error) {
299330
controlSock := filepath.Join(instDir, filenames.SSHSock)

0 commit comments

Comments
 (0)