Skip to content

Commit 1d086fc

Browse files
authored
feat(ssh-auth): add ssh-auth and forward into vm (#77)
This PR: 1. start a local ssh-auth server `$TMP/oo-ssh-agent-host.sock` linked to host `$SSH_AUTH_SOCK` 2. forward the local ssh-auth socket to vm `/ssh_auth/oo-ssh-agent-remote.sock` over ssh when vm ready, means all connections happened in `/ssh_auth/oo-ssh-agent-remote.sock` will forward to local ssh-agent server `$TMP/oo-ssh-agent-host.sock`
1 parent 3a2d258 commit 1d086fc

File tree

7 files changed

+195
-27
lines changed

7 files changed

+195
-27
lines changed

cmd/bauklotze/machine/start.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package machine
55

66
import (
7+
"bauklotze/pkg/machine/channel"
78
"context"
89
"fmt"
910
"os"
@@ -12,20 +13,19 @@ import (
1213
"syscall"
1314
"time"
1415

16+
"github.com/sirupsen/logrus"
17+
"github.com/spf13/cobra"
18+
"golang.org/x/sync/errgroup"
19+
20+
"bauklotze/cmd/registry"
1521
"bauklotze/pkg/api/server"
1622
"bauklotze/pkg/machine"
1723
allFlag "bauklotze/pkg/machine/allflag"
1824
"bauklotze/pkg/machine/define"
25+
"bauklotze/pkg/machine/events"
1926
"bauklotze/pkg/machine/io"
2027
"bauklotze/pkg/machine/shim"
2128
"bauklotze/pkg/machine/vmconfig"
22-
23-
"github.com/sirupsen/logrus"
24-
"github.com/spf13/cobra"
25-
"golang.org/x/sync/errgroup"
26-
27-
"bauklotze/cmd/registry"
28-
"bauklotze/pkg/machine/events"
2929
"bauklotze/pkg/system"
3030
)
3131

@@ -132,6 +132,13 @@ func start(cmd *cobra.Command, args []string) error {
132132
return startMachine(ctx, mc)
133133
})
134134

135+
g.Go(func() error {
136+
if err := shim.TryStartSSHAuthService(ctx, mc); err != nil {
137+
logrus.Warnf("SSH auth service stop: %v", err)
138+
}
139+
return nil
140+
})
141+
135142
defer cleanUp(mc) // Clean tmp files at the end
136143
return g.Wait() //nolint:wrapcheck
137144
}
@@ -176,6 +183,7 @@ func startMachine(parentCtx context.Context, mc *vmconfig.MachineConfig) error {
176183
}
177184

178185
logrus.Infof("Machine start successful")
186+
channel.NotifyMachineReady()
179187

180188
return shim.Wait(ctx, mc) //nolint:wrapcheck
181189
}
@@ -198,9 +206,11 @@ func cleanUp(mc *vmconfig.MachineConfig) {
198206
}
199207

200208
func SyncDisk(mc *vmconfig.MachineConfig) {
201-
events.NotifyRun(events.SyncMachineDisk)
202-
if err := shim.DiskSync(mc); err != nil {
203-
logrus.Warnf("Failed to sync disk: %v", err)
204-
return
209+
if channel.IsVMReady() {
210+
events.NotifyRun(events.SyncMachineDisk)
211+
if err := shim.DiskSync(mc); err != nil {
212+
logrus.Warnf("Failed to sync disk: %v", err)
213+
return
214+
}
205215
}
206216
}

cmd/registry/registry.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package registry
55

66
import (
77
allFlag "bauklotze/pkg/machine/allflag"
8+
"bauklotze/pkg/machine/channel"
89
"bauklotze/pkg/machine/define"
910
"bauklotze/pkg/machine/events"
1011
"bauklotze/pkg/machine/io"
@@ -41,6 +42,7 @@ func GetExitCode() int {
4142

4243
func NotifyAndExit(code int) {
4344
events.NotifyExit()
45+
channel.Close()
4446
SetExitCode(code)
4547
os.Exit(GetExitCode())
4648
}

go.mod

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module bauklotze
22

3-
go 1.23.4
3+
go 1.24.0
4+
5+
toolchain go1.24.1
46

57
require (
68
github.com/Code-Hex/go-infinity-channel v1.0.0
@@ -15,14 +17,16 @@ require (
1517
github.com/go-playground/validator/v10 v10.25.0
1618
github.com/gorilla/mux v1.8.1
1719
github.com/json-iterator/go v1.1.12
20+
github.com/oomol-lab/ovm-ssh-agent v1.1.1-0.20250307113355-2fe4d500c53c
21+
github.com/oomol-lab/ssh-forward v0.0.0-20250311033012-057eea751dd7
1822
github.com/prashantgupta24/mac-sleep-notifier v1.0.1
1923
github.com/shirou/gopsutil/v3 v3.24.5
2024
github.com/sirupsen/logrus v1.9.3
2125
github.com/spf13/cobra v1.8.1
2226
github.com/stretchr/testify v1.10.0
23-
golang.org/x/crypto v0.35.0
24-
golang.org/x/sync v0.11.0
25-
golang.org/x/sys v0.30.0
27+
golang.org/x/crypto v0.36.0
28+
golang.org/x/sync v0.12.0
29+
golang.org/x/sys v0.31.0
2630
)
2731

2832
require (
@@ -48,7 +52,7 @@ require (
4852
github.com/tklauser/numcpus v0.6.1 // indirect
4953
github.com/yusufpapurcu/wmi v1.2.4 // indirect
5054
golang.org/x/net v0.35.0 // indirect
51-
golang.org/x/text v0.22.0 // indirect
55+
golang.org/x/text v0.23.0 // indirect
5256
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
5357
gopkg.in/yaml.v3 v3.0.1 // indirect
5458
)

go.sum

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
6464
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
6565
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
6666
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
67+
github.com/oomol-lab/ovm-ssh-agent v1.1.1-0.20250307080813-33a2addd8d70 h1:mwoDZHyzkai9/Ho4SqHTn3ZNjeairpDAjxBwbZzlYxg=
68+
github.com/oomol-lab/ovm-ssh-agent v1.1.1-0.20250307080813-33a2addd8d70/go.mod h1:UiWnuQCSmt3Zta1LctVpp+ETEaJv08OA+Bkp45uyM/M=
69+
github.com/oomol-lab/ovm-ssh-agent v1.1.1-0.20250307113355-2fe4d500c53c h1:kbYQjlZ0eKNiUPjX99nudcbvh2z86TF3hM0jAvytApo=
70+
github.com/oomol-lab/ovm-ssh-agent v1.1.1-0.20250307113355-2fe4d500c53c/go.mod h1:UiWnuQCSmt3Zta1LctVpp+ETEaJv08OA+Bkp45uyM/M=
71+
github.com/oomol-lab/ssh-forward v0.0.0-20250306133922-a0a693bfc5f6 h1:uJP1EoqmP88TOqA6PbpJlzt0eyE27WZ6XAefuc3H4T4=
72+
github.com/oomol-lab/ssh-forward v0.0.0-20250306133922-a0a693bfc5f6/go.mod h1:/X60uEwaCA1Bt0AJ+6VShkjErRBbkDbzmqwxR8jPpMI=
73+
github.com/oomol-lab/ssh-forward v0.0.0-20250310111812-cc1e7c2d1876 h1:9HgxxAcKJx9U6w8oRTtIIc7Tdp7F55p5yUTJlMqahtc=
74+
github.com/oomol-lab/ssh-forward v0.0.0-20250310111812-cc1e7c2d1876/go.mod h1:/X60uEwaCA1Bt0AJ+6VShkjErRBbkDbzmqwxR8jPpMI=
75+
github.com/oomol-lab/ssh-forward v0.0.0-20250311033012-057eea751dd7 h1:T+1ULZnf+KeO/w3kTuv5zQwllJjISB0UTKvNR4CHtwc=
76+
github.com/oomol-lab/ssh-forward v0.0.0-20250311033012-057eea751dd7/go.mod h1:/X60uEwaCA1Bt0AJ+6VShkjErRBbkDbzmqwxR8jPpMI=
6777
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
6878
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6979
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
@@ -100,24 +110,24 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
100110
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
101111
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
102112
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
103-
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
104-
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
113+
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
114+
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
105115
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
106116
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
107-
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
108-
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
117+
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
118+
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
109119
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
110120
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
111121
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
112122
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
113123
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
114124
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
115-
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
116-
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
117-
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
118-
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
119-
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
120-
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
125+
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
126+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
127+
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
128+
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
129+
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
130+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
121131
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
122132
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
123133
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

pkg/api/server/handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc {
7575

7676
func (s *APIServer) apiWrapper(h http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
7777
if err := r.ParseForm(); err != nil {
78-
logrus.Errorf("Failed Request: unable to parse form: " + err.Error())
78+
logrus.Errorf("Failed Request: unable to parse form: %v", err)
7979
}
8080
h(w, r)
8181
}

pkg/machine/channel/channel.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package channel
2+
3+
import "context"
4+
5+
var (
6+
vmReady context.Context
7+
vmCancel context.CancelFunc
8+
)
9+
10+
func init() {
11+
vmReady, vmCancel = context.WithCancel(context.Background()) //nolint:fatcontext
12+
}
13+
14+
func NotifyMachineReady() {
15+
vmCancel()
16+
}
17+
18+
func WaitVMReady() <-chan struct{} {
19+
return vmReady.Done()
20+
}
21+
22+
func IsVMReady() bool {
23+
select {
24+
case <-vmReady.Done():
25+
return true
26+
default:
27+
return false
28+
}
29+
}
30+
31+
func Close() {
32+
vmCancel()
33+
}

pkg/machine/shim/ssh_auth.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-FileCopyrightText: 2024-2025 OOMOL, Inc. <https://www.oomol.com>
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package shim
5+
6+
import (
7+
"bauklotze/pkg/machine/channel"
8+
"bauklotze/pkg/machine/define"
9+
"bauklotze/pkg/machine/vmconfig"
10+
"context"
11+
"fmt"
12+
"net"
13+
"os"
14+
"path/filepath"
15+
16+
"golang.org/x/sync/errgroup"
17+
18+
"github.com/oomol-lab/ovm-ssh-agent/pkg/sshagent"
19+
"github.com/oomol-lab/ovm-ssh-agent/pkg/system"
20+
21+
"github.com/oomol-lab/ovm-ssh-agent/pkg/identity"
22+
forwarder "github.com/oomol-lab/ssh-forward"
23+
24+
"github.com/sirupsen/logrus"
25+
)
26+
27+
func TryStartSSHAuthService(ctx context.Context, mc *vmconfig.MachineConfig) error {
28+
select {
29+
case <-ctx.Done():
30+
return fmt.Errorf("cancel TryStartSSHAuthService cause: %w", context.Cause(ctx))
31+
case <-channel.WaitVMReady():
32+
break
33+
}
34+
35+
logrus.Infoln("Start SSH auth service")
36+
return startSSHAuthServiceAndForward(ctx, mc)
37+
}
38+
39+
func startSSHAuthServiceAndForward(ctx context.Context, mc *vmconfig.MachineConfig) error {
40+
ctx, cancel := context.WithCancelCause(ctx)
41+
defer cancel(context.Canceled)
42+
43+
g, ctx := errgroup.WithContext(ctx)
44+
45+
localSocketFile := filepath.Join(mc.Dirs.SocksDir.GetPath(), "oo-ssh-agent-host.sock")
46+
if err := os.Remove(localSocketFile); err != nil && !os.IsNotExist(err) {
47+
return fmt.Errorf("failed to remove local ssh agent socket: %w", err)
48+
}
49+
50+
listener, err := net.Listen("unix", localSocketFile)
51+
if err != nil {
52+
return fmt.Errorf("failed to listen unix socket: %w", err)
53+
}
54+
defer listener.Close()
55+
56+
upstreamSocket := system.GetSSHAgent()
57+
if upstreamSocket == "" {
58+
return fmt.Errorf("upstream SSH agent socket empty")
59+
}
60+
logrus.Infof("upstream ssh agent listened in: %q", upstreamSocket)
61+
62+
ooSSHAgent, err := sshagent.NewSSHAgent(ctx, upstreamSocket)
63+
if err != nil {
64+
return fmt.Errorf("failed to create oo ssh agent: %w", err)
65+
}
66+
defer ooSSHAgent.Close()
67+
68+
// find local private keys ~/.ssh
69+
ooSSHAgent.LoadLocalKeys(identity.FindPrivateKeys()...)
70+
71+
g.Go(func() error {
72+
return ooSSHAgent.Serve(listener)
73+
})
74+
75+
remoteSocketFile := "/opt/ssh_auth/oo-ssh-agent.sock"
76+
logrus.Infof("forward unix socket %q to %q", localSocketFile,
77+
fmt.Sprintf("%s@%s:%d:[%s]", mc.SSH.RemoteUsername, define.LocalHostURL, mc.SSH.Port, remoteSocketFile))
78+
79+
socketForwarder := forwarder.NewUnixRemote(localSocketFile, define.LocalHostURL, remoteSocketFile)
80+
socketForwarder.SetTunneledConnState(func(tun *forwarder.ForwardConfig, state *forwarder.TunneledConnState) {
81+
logrus.Infof("connect state: %v", state)
82+
})
83+
84+
socketForwarder.SetKeyFile(mc.SSH.IdentityPath)
85+
socketForwarder.SetUser(mc.SSH.RemoteUsername)
86+
socketForwarder.SetPort(mc.SSH.Port)
87+
88+
// We set a callback to know when the tunnel is ready
89+
socketForwarder.SetConnState(func(tun *forwarder.ForwardConfig, state forwarder.ConnState) {
90+
switch state {
91+
case forwarder.StateStarting:
92+
logrus.Infof("socket forwarder state is staring")
93+
logrus.Infof("clean target socket file:%s", socketForwarder.Remote.String())
94+
if err := socketForwarder.CleanTargetSocketFile(); err != nil {
95+
cancel(fmt.Errorf("failed to clean target socket file: %w", err))
96+
}
97+
case forwarder.StateStarted:
98+
logrus.Infoln("socket forwarder state is: started")
99+
case forwarder.StateStopped:
100+
logrus.Infoln("socket forwarder state is: stopped")
101+
}
102+
})
103+
104+
g.Go(func() error {
105+
return socketForwarder.Start(ctx)
106+
})
107+
108+
return g.Wait() //nolint:wrapcheck
109+
}

0 commit comments

Comments
 (0)