Skip to content

Commit f2e1e8b

Browse files
committed
fix(ssh): wire embedded mode port/key handling across up/ssh/ports
1 parent a37c676 commit f2e1e8b

5 files changed

Lines changed: 98 additions & 16 deletions

File tree

.github/workflows/syncthing-image.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
- name: Build and push
5757
uses: docker/build-push-action@v6
5858
with:
59-
context: infra/sidecar
59+
context: .
6060
file: infra/sidecar/Dockerfile
6161
platforms: linux/amd64,linux/arm64
6262
push: true

internal/cli/common.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ import (
2222
)
2323

2424
const sessionHeartbeatInterval = 5 * time.Minute
25+
const (
26+
sshModeSidecar = "sidecar"
27+
sshModeEmbedded = "embedded"
28+
embeddedSSHPort = 2222
29+
)
2530

2631
var invalidOwnerChars = regexp.MustCompile(`[^a-z0-9._-]`)
2732

@@ -170,6 +175,38 @@ func annotationsForSession(cfg *config.DevEnvironment) map[string]string {
170175
return out
171176
}
172177

178+
func normalizeSSHMode(mode string) string {
179+
switch strings.ToLower(strings.TrimSpace(mode)) {
180+
case sshModeEmbedded:
181+
return sshModeEmbedded
182+
default:
183+
return sshModeSidecar
184+
}
185+
}
186+
187+
func sshRemotePortForMode(cfg *config.DevEnvironment, mode string) int {
188+
if normalizeSSHMode(mode) == sshModeEmbedded {
189+
return embeddedSSHPort
190+
}
191+
if cfg != nil && cfg.Spec.SSH.RemotePort > 0 {
192+
return cfg.Spec.SSH.RemotePort
193+
}
194+
return 22
195+
}
196+
197+
func detectSessionSSHMode(opts *Options, namespace, sessionName string) (string, error) {
198+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
199+
defer cancel()
200+
mode, err := newKubeClient(opts).GetPodAnnotation(ctx, namespace, podName(sessionName), "okdev.io/ssh-mode")
201+
if err != nil {
202+
if apierrors.IsNotFound(err) {
203+
return sshModeSidecar, nil
204+
}
205+
return "", err
206+
}
207+
return normalizeSSHMode(mode), nil
208+
}
209+
173210
func podName(sessionName string) string {
174211
return "okdev-" + sessionName
175212
}

internal/cli/ports.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ func newPortsCmd(opts *Options) *cobra.Command {
3030
if err := ensureSessionOwnership(opts, k, ns, sn, true); err != nil {
3131
return err
3232
}
33+
sshMode, modeErr := detectSessionSSHMode(opts, ns, sn)
34+
if modeErr != nil {
35+
return fmt.Errorf("detect ssh mode: %w", modeErr)
36+
}
37+
effectiveSSHPort := sshRemotePortForMode(cfg, sshMode)
3338
stopMaintenance := startSessionMaintenance(opts, cfg, ns, sn, cmd.OutOrStdout(), true, true)
3439
defer stopMaintenance()
3540
if len(cfg.Spec.Ports) == 0 {
@@ -50,14 +55,14 @@ func newPortsCmd(opts *Options) *cobra.Command {
5055
if err != nil {
5156
return fmt.Errorf("resolve SSH key path: %w", err)
5257
}
53-
if err := ensureSSHKeyOnPod(opts, cfg, ns, podName(sn), keyPath); err != nil {
58+
if err := ensureSSHKeyOnPod(opts, cfg, ns, podName(sn), keyPath, sshMode); err != nil {
5459
return fmt.Errorf("setup SSH key in pod: %w", err)
5560
}
56-
if err := waitForSSHDReady(opts, cfg, ns, podName(sn), 20*time.Second); err != nil {
61+
if err := waitForSSHDReady(opts, cfg, ns, podName(sn), sshMode, 20*time.Second); err != nil {
5762
return fmt.Errorf("wait for sshd ready: %w", err)
5863
}
5964
alias := sshHostAlias(sn)
60-
changed, err := ensureSSHConfigEntry(alias, sn, ns, cfg.Spec.SSH.User, cfg.Spec.SSH.RemotePort, keyPath, cfgPath, cfg.Spec.Ports, cfg.Spec.SSH)
65+
changed, err := ensureSSHConfigEntry(alias, sn, ns, cfg.Spec.SSH.User, effectiveSSHPort, keyPath, cfgPath, cfg.Spec.Ports, cfg.Spec.SSH)
6166
if err != nil {
6267
return fmt.Errorf("update ~/.ssh/config for managed forwards: %w", err)
6368
}

internal/cli/ssh.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,18 @@ func newSSHCmd(opts *Options) *cobra.Command {
4747
if err := ensureSessionOwnership(opts, k, ns, sn, true); err != nil {
4848
return err
4949
}
50+
sshMode, modeErr := detectSessionSSHMode(opts, ns, sn)
51+
if modeErr != nil {
52+
return fmt.Errorf("detect ssh mode: %w", modeErr)
53+
}
5054
stopMaintenance := startSessionMaintenance(opts, cfg, ns, sn, cmd.OutOrStdout(), true, true)
5155
defer stopMaintenance()
5256

5357
if user == "" {
5458
user = cfg.Spec.SSH.User
5559
}
5660
if remotePort == 0 {
57-
remotePort = cfg.Spec.SSH.RemotePort
61+
remotePort = sshRemotePortForMode(cfg, sshMode)
5862
}
5963
if localPort == 0 {
6064
localPort = 2222
@@ -67,11 +71,11 @@ func newSSHCmd(opts *Options) *cobra.Command {
6771
}
6872

6973
if setupKey {
70-
if err := ensureSSHKeyOnPod(opts, cfg, ns, podName(sn), keyPath); err != nil {
74+
if err := ensureSSHKeyOnPod(opts, cfg, ns, podName(sn), keyPath, sshMode); err != nil {
7175
return err
7276
}
7377
}
74-
if err := waitForSSHDReady(opts, cfg, ns, podName(sn), 20*time.Second); err != nil {
78+
if err := waitForSSHDReady(opts, cfg, ns, podName(sn), sshMode, 20*time.Second); err != nil {
7579
fmt.Fprintf(cmd.ErrOrStderr(), "warning: sshd not ready yet: %v\n", err)
7680
}
7781

@@ -248,7 +252,7 @@ func newSSHCmd(opts *Options) *cobra.Command {
248252
return cmd
249253
}
250254

251-
func ensureSSHKeyOnPod(opts *Options, cfg *config.DevEnvironment, namespace, pod, keyPath string) error {
255+
func ensureSSHKeyOnPod(opts *Options, cfg *config.DevEnvironment, namespace, pod, keyPath, sshMode string) error {
252256
if err := ensureCommand("ssh-keygen"); err != nil {
253257
return err
254258
}
@@ -275,6 +279,7 @@ func ensureSSHKeyOnPod(opts *Options, cfg *config.DevEnvironment, namespace, pod
275279
k := newKubeClient(opts)
276280
container := sshTargetContainer(cfg)
277281
var lastErr error
282+
installed := false
278283
for i := 0; i < 3; i++ {
279284
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
280285
if container == "" {
@@ -284,25 +289,58 @@ func ensureSSHKeyOnPod(opts *Options, cfg *config.DevEnvironment, namespace, pod
284289
}
285290
cancel()
286291
if err == nil {
287-
return nil
292+
installed = true
293+
break
288294
}
289295
lastErr = err
290296
time.Sleep(time.Duration(i+1) * 500 * time.Millisecond)
291297
}
292-
return fmt.Errorf("install ssh key in pod: %w", lastErr)
298+
if !installed {
299+
return fmt.Errorf("install ssh key in pod: %w", lastErr)
300+
}
301+
302+
if normalizeSSHMode(sshMode) == sshModeEmbedded {
303+
copyScript := `set -eu
304+
DEV_PID=""
305+
for pid in $(ls /proc 2>/dev/null | grep -E '^[0-9]+$' | sort -n); do
306+
[ "$pid" = "1" ] && continue
307+
[ "$pid" = "$$" ] && continue
308+
[ -r "/proc/$pid/root" ] 2>/dev/null || continue
309+
if ! [ "/proc/$pid/root" -ef "/proc/self/root" ] 2>/dev/null; then
310+
if [ -d "/proc/$pid" ]; then
311+
DEV_PID="$pid"
312+
break
313+
fi
314+
fi
315+
done
316+
[ -n "$DEV_PID" ]
317+
nsenter --target "$DEV_PID" --mount -- mkdir -p /var/okdev
318+
cat /root/.ssh/authorized_keys | nsenter --target "$DEV_PID" --mount -- sh -c "cat > /var/okdev/authorized_keys && chmod 600 /var/okdev/authorized_keys"`
319+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
320+
_, err := k.ExecShInContainer(ctx, namespace, pod, "okdev-sidecar", copyScript)
321+
cancel()
322+
if err != nil {
323+
return fmt.Errorf("sync embedded ssh authorized_keys: %w", err)
324+
}
325+
}
326+
return nil
293327
}
294328

295-
func waitForSSHDReady(opts *Options, cfg *config.DevEnvironment, namespace, pod string, timeout time.Duration) error {
329+
func waitForSSHDReady(opts *Options, cfg *config.DevEnvironment, namespace, pod, sshMode string, timeout time.Duration) error {
296330
k := newKubeClient(opts)
297331
container := sshTargetContainer(cfg)
298332
if container == "" {
299333
return nil
300334
}
335+
probeCmd := "ps | grep '[s]shd' >/dev/null 2>&1"
336+
if normalizeSSHMode(sshMode) == sshModeEmbedded {
337+
probeCmd = "ps | grep '[o]kdev-sshd' >/dev/null 2>&1"
338+
}
301339
deadline := time.Now().Add(timeout)
302340
var lastErr error
303341
for time.Now().Before(deadline) {
304342
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
305-
_, err := k.ExecShInContainer(ctx, namespace, pod, container, "ps | grep '[s]shd' >/dev/null 2>&1")
343+
_, err := k.ExecShInContainer(ctx, namespace, pod, container, probeCmd)
306344
cancel()
307345
if err == nil {
308346
return nil

internal/cli/up.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func newUpCmd(opts *Options) *cobra.Command {
5959
ui.stepDone("namespace", ns)
6060
labels := labelsForSession(opts, cfg, sn)
6161
annotations := annotationsForSession(cfg)
62+
annotations["okdev.io/ssh-mode"] = normalizeSSHMode(sshMode)
6263
volumes := cfg.EffectiveVolumes()
6364
pod := podName(sn)
6465
if dryRun {
@@ -135,22 +136,23 @@ func newUpCmd(opts *Options) *cobra.Command {
135136

136137
ui.section("Setup")
137138
sshSummary := "disabled"
138-
if cfg.Spec.SSH.RemotePort > 0 {
139+
effectiveSSHPort := sshRemotePortForMode(cfg, sshMode)
140+
if effectiveSSHPort > 0 {
139141
keyPath, keyErr := defaultSSHKeyPath(cfg)
140142
if keyErr != nil {
141143
ui.warnf("failed to resolve SSH key path: %v", keyErr)
142144
sshSummary = "degraded (key path error)"
143145
} else {
144-
if err := ensureSSHKeyOnPod(opts, cfg, ns, pod, keyPath); err != nil {
146+
if err := ensureSSHKeyOnPod(opts, cfg, ns, pod, keyPath, sshMode); err != nil {
145147
ui.warnf("failed to setup SSH key in pod: %v", err)
146148
sshSummary = "degraded (key setup failed)"
147149
}
148-
if err := waitForSSHDReady(opts, cfg, ns, pod, 20*time.Second); err != nil {
150+
if err := waitForSSHDReady(opts, cfg, ns, pod, sshMode, 20*time.Second); err != nil {
149151
ui.warnf("sshd not ready yet: %v", err)
150152
sshSummary = "degraded (sshd not ready)"
151153
}
152154
alias := sshHostAlias(sn)
153-
if _, cfgErr := ensureSSHConfigEntry(alias, sn, ns, cfg.Spec.SSH.User, cfg.Spec.SSH.RemotePort, keyPath, cfgPath, cfg.Spec.Ports, cfg.Spec.SSH); cfgErr != nil {
155+
if _, cfgErr := ensureSSHConfigEntry(alias, sn, ns, cfg.Spec.SSH.User, effectiveSSHPort, keyPath, cfgPath, cfg.Spec.Ports, cfg.Spec.SSH); cfgErr != nil {
154156
ui.warnf("failed to update ~/.ssh/config: %v", cfgErr)
155157
sshSummary = "degraded (ssh config update failed)"
156158
} else {

0 commit comments

Comments
 (0)