Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit c1a9994

Browse files
authored
Add coder tunnel command (#313)
* Add coder tunnel command * Fix auth for p2p
1 parent 739c8e4 commit c1a9994

File tree

5 files changed

+330
-20
lines changed

5 files changed

+330
-20
lines changed

Diff for: docs/coder_config-ssh.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ coder config-ssh [flags]
1515
```
1616
--filepath string override the default path of your ssh config file (default "~/.ssh/config")
1717
-h, --help help for config-ssh
18+
--p2p (experimental) uses coder tunnel to proxy ssh connection
1819
--remove remove the auto-generated Coder ssh config
1920
```
2021

Diff for: internal/cmd/agent.go

+34-14
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"golang.org/x/xerrors"
2020
"nhooyr.io/websocket"
2121

22-
"cdr.dev/coder-cli/internal/x/xcobra"
2322
"cdr.dev/coder-cli/internal/x/xwebrtc"
2423
"cdr.dev/coder-cli/pkg/proto"
2524
)
@@ -40,31 +39,42 @@ func agentCmd() *cobra.Command {
4039

4140
func startCmd() *cobra.Command {
4241
var (
43-
token string
42+
token string
43+
coderURL string
4444
)
4545
cmd := &cobra.Command{
46-
Use: "start [coderURL] --token=[token]",
47-
Args: xcobra.ExactArgs(1),
46+
Use: "start --coder-url=[coder_url] --token=[token]",
4847
Short: "starts the coder agent",
4948
Long: "starts the coder agent",
50-
Example: `# start the agent and connect with a Coder agent token
49+
Example: `# start the agent and use CODER_URL and CODER_AGENT_TOKEN env vars
5150
52-
coder agent start https://my-coder.com --token xxxx-xxxx
51+
coder agent start
5352
54-
# start the agent and use CODER_AGENT_TOKEN env var for auth token
53+
# start the agent and connect with a specified url and agent token
5554
56-
coder agent start https://my-coder.com
55+
coder agent start --coder-url https://my-coder.com --token xxxx-xxxx
5756
`,
5857
RunE: func(cmd *cobra.Command, args []string) error {
5958
ctx := cmd.Context()
6059
log := slog.Make(sloghuman.Sink(cmd.OutOrStdout()))
6160

62-
// Pull the URL from the args and do some sanity check.
63-
rawURL := args[0]
64-
if rawURL == "" || !strings.HasPrefix(rawURL, "http") {
61+
if coderURL == "" {
62+
var ok bool
63+
token, ok = os.LookupEnv("CODER_URL")
64+
if !ok {
65+
client, err := newClient(ctx)
66+
if err != nil {
67+
return xerrors.New("must login, pass --coder-url flag, or set the CODER_URL env variable")
68+
}
69+
burl := client.BaseURL()
70+
coderURL = burl.String()
71+
}
72+
}
73+
74+
if !strings.HasPrefix(coderURL, "http") {
6575
return xerrors.Errorf("invalid URL")
6676
}
67-
u, err := url.Parse(rawURL)
77+
u, err := url.Parse(coderURL)
6878
if err != nil {
6979
return xerrors.Errorf("parse url: %w", err)
7080
}
@@ -79,18 +89,26 @@ coder agent start https://my-coder.com
7989
}
8090
}
8191

92+
if token == "" {
93+
var ok bool
94+
token, ok = os.LookupEnv("CODER_AGENT_TOKEN")
95+
if !ok {
96+
return xerrors.New("must pass --token or set the CODER_AGENT_TOKEN env variable")
97+
}
98+
}
99+
82100
q := u.Query()
83101
q.Set("service_token", token)
84102
u.RawQuery = q.Encode()
85103

86104
ctx, cancelFunc := context.WithTimeout(ctx, time.Second*15)
87105
defer cancelFunc()
88106
log.Info(ctx, "connecting to broker", slog.F("url", u.String()))
89-
conn, res, err := websocket.Dial(ctx, u.String(), nil)
107+
// nolint: bodyclose
108+
conn, _, err := websocket.Dial(ctx, u.String(), nil)
90109
if err != nil {
91110
return fmt.Errorf("dial: %w", err)
92111
}
93-
_ = res.Body.Close()
94112
nc := websocket.NetConn(context.Background(), conn, websocket.MessageBinary)
95113
session, err := yamux.Server(nc, nil)
96114
if err != nil {
@@ -112,6 +130,8 @@ coder agent start https://my-coder.com
112130
}
113131

114132
cmd.Flags().StringVar(&token, "token", "", "coder agent token")
133+
cmd.Flags().StringVar(&coderURL, "coder-url", "", "coder access url")
134+
115135
return cmd
116136
}
117137

Diff for: internal/cmd/cmd.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func Make() *cobra.Command {
3838
providersCmd(),
3939
genDocsCmd(app),
4040
agentCmd(),
41+
tunnelCmd(),
4142
)
4243
app.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show verbose output")
4344
return app

Diff for: internal/cmd/configssh.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,23 @@ func configSSHCmd() *cobra.Command {
3535
var (
3636
configpath string
3737
remove = false
38+
p2p = false
3839
)
3940

4041
cmd := &cobra.Command{
4142
Use: "config-ssh",
4243
Short: "Configure SSH to access Coder environments",
4344
Long: "Inject the proper OpenSSH configuration into your local SSH config file.",
44-
RunE: configSSH(&configpath, &remove),
45+
RunE: configSSH(&configpath, &remove, &p2p),
4546
}
4647
cmd.Flags().StringVar(&configpath, "filepath", filepath.Join("~", ".ssh", "config"), "override the default path of your ssh config file")
4748
cmd.Flags().BoolVar(&remove, "remove", false, "remove the auto-generated Coder ssh config")
49+
cmd.Flags().BoolVar(&p2p, "p2p", false, "(experimental) uses coder tunnel to proxy ssh connection")
4850

4951
return cmd
5052
}
5153

52-
func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []string) error {
54+
func configSSH(configpath *string, remove *bool, p2p *bool) func(cmd *cobra.Command, _ []string) error {
5355
return func(cmd *cobra.Command, _ []string) error {
5456
ctx := cmd.Context()
5557
usr, err := user.Current()
@@ -113,7 +115,7 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
113115
return xerrors.New("SSH is disabled or not available for any environments in your Coder deployment.")
114116
}
115117

116-
newConfig := makeNewConfigs(user.Username, envsWithProviders, privateKeyFilepath)
118+
newConfig := makeNewConfigs(user.Username, envsWithProviders, privateKeyFilepath, *p2p)
117119

118120
err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm)
119121
if err != nil {
@@ -174,7 +176,7 @@ func writeSSHKey(ctx context.Context, client coder.Client, privateKeyPath string
174176
return ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKey), 0600)
175177
}
176178

177-
func makeNewConfigs(userName string, envs []coderutil.EnvWithWorkspaceProvider, privateKeyFilepath string) string {
179+
func makeNewConfigs(userName string, envs []coderutil.EnvWithWorkspaceProvider, privateKeyFilepath string, p2p bool) string {
178180
newConfig := fmt.Sprintf("\n%s\n%s\n\n", sshStartToken, sshStartMessage)
179181

180182
sort.Slice(envs, func(i, j int) bool { return envs[i].Env.Name < envs[j].Env.Name })
@@ -192,14 +194,27 @@ func makeNewConfigs(userName string, envs []coderutil.EnvWithWorkspaceProvider,
192194
clog.LogWarn("invalid access url", clog.Causef("malformed url: %q", env.WorkspaceProvider.EnvproxyAccessURL))
193195
continue
194196
}
195-
newConfig += makeSSHConfig(u.Host, userName, env.Env.Name, privateKeyFilepath)
197+
newConfig += makeSSHConfig(u.Host, userName, env.Env.Name, privateKeyFilepath, p2p)
196198
}
197199
newConfig += fmt.Sprintf("\n%s\n", sshEndToken)
198200

199201
return newConfig
200202
}
201203

202-
func makeSSHConfig(host, userName, envName, privateKeyFilepath string) string {
204+
func makeSSHConfig(host, userName, envName, privateKeyFilepath string, p2p bool) string {
205+
if p2p {
206+
return fmt.Sprintf(
207+
`Host coder.%s
208+
HostName localhost
209+
ProxyCommand coder tunnel %s 22 stdio
210+
StrictHostKeyChecking no
211+
ConnectTimeout=0
212+
IdentityFile="%s"
213+
ServerAliveInterval 60
214+
ServerAliveCountMax 3
215+
`, envName, envName, privateKeyFilepath)
216+
}
217+
203218
return fmt.Sprintf(
204219
`Host coder.%s
205220
HostName %s

0 commit comments

Comments
 (0)