-
Notifications
You must be signed in to change notification settings - Fork 325
/
ssh_server.go
136 lines (117 loc) · 3.44 KB
/
ssh_server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package helper
import (
"encoding/base64"
"fmt"
"os"
"time"
"github.com/loft-sh/devpod/cmd/flags"
"github.com/loft-sh/devpod/pkg/agent"
helperssh "github.com/loft-sh/devpod/pkg/ssh/server"
"github.com/loft-sh/devpod/pkg/ssh/server/port"
"github.com/loft-sh/devpod/pkg/stdio"
"github.com/loft-sh/devpod/pkg/token"
"github.com/loft-sh/log"
"github.com/loft-sh/ssh"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// SSHServerCmd holds the ssh server cmd flags
type SSHServerCmd struct {
*flags.GlobalFlags
Token string
Address string
Stdio bool
TrackActivity bool
Workdir string
}
// NewSSHServerCmd creates a new ssh command
func NewSSHServerCmd(flags *flags.GlobalFlags) *cobra.Command {
cmd := &SSHServerCmd{
GlobalFlags: flags,
}
sshCmd := &cobra.Command{
Use: "ssh-server",
Short: "Starts a new ssh server",
Args: cobra.NoArgs,
RunE: cmd.Run,
}
sshCmd.Flags().StringVar(&cmd.Address, "address", fmt.Sprintf("0.0.0.0:%d", helperssh.DefaultPort), "Address to listen to")
sshCmd.Flags().BoolVar(&cmd.Stdio, "stdio", false, "Will listen on stdout and stdin instead of an address")
sshCmd.Flags().BoolVar(&cmd.TrackActivity, "track-activity", false, "If enabled will write the last activity time to a file")
sshCmd.Flags().StringVar(&cmd.Token, "token", "", "Base64 encoded token to use")
sshCmd.Flags().StringVar(&cmd.Workdir, "workdir", "", "Directory where commands will run on the host")
return sshCmd
}
// Run runs the command logic
func (cmd *SSHServerCmd) Run(_ *cobra.Command, _ []string) error {
var (
keys []ssh.PublicKey
hostKey []byte
)
if cmd.Token != "" {
// parse token
t, err := token.ParseToken(cmd.Token)
if err != nil {
return errors.Wrap(err, "parse token")
}
if t.AuthorizedKeys != "" {
keyBytes, err := base64.StdEncoding.DecodeString(t.AuthorizedKeys)
if err != nil {
return fmt.Errorf("seems like the provided encoded string is not base64 encoded")
}
for len(keyBytes) > 0 {
key, _, _, rest, err := ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
return errors.Wrap(err, "parse authorized key")
}
keys = append(keys, key)
keyBytes = rest
}
}
if len(t.HostKey) > 0 {
var err error
hostKey, err = base64.StdEncoding.DecodeString(t.HostKey)
if err != nil {
return fmt.Errorf("decode host key")
}
}
}
// start the server
server, err := helperssh.NewServer(cmd.Address, hostKey, keys, cmd.Workdir, log.Default.ErrorStreamOnly())
if err != nil {
return err
}
// should we listen on stdout & stdin?
if cmd.Stdio {
if cmd.TrackActivity {
go func() {
_, err = os.Stat(agent.ContainerActivityFile)
if err != nil {
err = os.WriteFile(agent.ContainerActivityFile, nil, 0o777)
if err != nil {
fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err)
return
}
_ = os.Chmod(agent.ContainerActivityFile, 0o777)
}
for {
time.Sleep(time.Second * 10)
file, _ := os.Create(agent.ContainerActivityFile)
file.Close()
}
}()
}
lis := stdio.NewStdioListener(os.Stdin, os.Stdout, true)
return server.Serve(lis)
}
// check if ssh is already running at that port
available, err := port.IsAvailable(cmd.Address)
if !available {
if err != nil {
return fmt.Errorf("address %s already in use: %w", cmd.Address, err)
}
log.Default.ErrorStreamOnly().Debugf("address %s already in use", cmd.Address)
return nil
}
return server.ListenAndServe()
}