mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add Tailscale networking (#3505)
* fix: Add coder user to docker group on installation This makes for a simpler setup, and reduces the likelihood a user runs into a strange issue. * Add wgnet * Add ping * Add listening * Finish refactor to make this work * Add interface for swapping * Fix conncache with interface * chore: update gvisor * fix tailscale types * linting * more linting * Add coordinator * Add coordinator tests * Fix coordination * It compiles! * Move all connection negotiation in-memory * Migrate coordinator to use net.conn * Add closed func * Fix close listener func * Make reconnecting PTY work * Fix reconnecting PTY * Update CI to Go 1.19 * Add CLI flags for DERP mapping * Fix Tailnet test * Rename ConnCoordinator to TailnetCoordinator * Remove print statement from workspace agent test * Refactor wsconncache to use tailnet * Remove STUN from unit tests * Add migrate back to dump * chore: Upgrade to Go 1.19 This is required as part of #3505. * Fix reconnecting PTY tests * fix: update wireguard-go to fix devtunnel * fix migration numbers * linting * Return early for status if endpoints are empty * Update cli/server.go Co-authored-by: Colin Adler <colin1adler@gmail.com> * Update cli/server.go Co-authored-by: Colin Adler <colin1adler@gmail.com> * Fix frontend entites * Fix agent bicopy * Fix race condition for the last node * Fix down migration * Fix connection RBAC * Fix migration numbers * Fix forwarding TCP to a local port * Implement ping for tailnet * Rename to ForceHTTP * Add external derpmapping * Expose DERP region names to the API * Add global option to enable Tailscale networking for web * Mark DERP flags hidden while testing * Update DERP map on reconnect * Add close func to workspace agents * Fix race condition in upstream dependency * Fix feature columns race condition Co-authored-by: Colin Adler <colin1adler@gmail.com>
This commit is contained in:
co-authored by
Colin Adler
parent
00da01fdf7
commit
9bd83e5ec7
+31
-85
@@ -19,18 +19,16 @@ import (
|
||||
gosshagent "golang.org/x/crypto/ssh/agent"
|
||||
"golang.org/x/term"
|
||||
"golang.org/x/xerrors"
|
||||
"inet.af/netaddr"
|
||||
tslogger "tailscale.com/types/logger"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/sloghuman"
|
||||
|
||||
"github.com/coder/coder/agent"
|
||||
"github.com/coder/coder/cli/cliflag"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/autobuild/notify"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/coder/peer/peerwg"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -90,87 +88,35 @@ func ssh() *cobra.Command {
|
||||
return xerrors.Errorf("await agent: %w", err)
|
||||
}
|
||||
|
||||
var newSSHClient func() (*gossh.Client, error)
|
||||
|
||||
var conn agent.Conn
|
||||
if !wireguard {
|
||||
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, nil)
|
||||
conn, err = client.DialWorkspaceAgent(ctx, workspaceAgent.ID, nil)
|
||||
} else {
|
||||
conn, err = client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, workspaceAgent.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
stopPolling := tryPollWorkspaceAutostop(ctx, client, workspace)
|
||||
defer stopPolling()
|
||||
|
||||
if stdio {
|
||||
rawSSH, err := conn.SSH()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
defer rawSSH.Close()
|
||||
|
||||
stopPolling := tryPollWorkspaceAutostop(ctx, client, workspace)
|
||||
defer stopPolling()
|
||||
|
||||
if stdio {
|
||||
rawSSH, err := conn.SSH()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rawSSH.Close()
|
||||
|
||||
go func() {
|
||||
_, _ = io.Copy(cmd.OutOrStdout(), rawSSH)
|
||||
}()
|
||||
_, _ = io.Copy(rawSSH, cmd.InOrStdin())
|
||||
return nil
|
||||
}
|
||||
|
||||
newSSHClient = conn.SSHClient
|
||||
} else {
|
||||
// TODO: more granual control of Tailscale logging.
|
||||
peerwg.Logf = tslogger.Discard
|
||||
|
||||
ipv6 := peerwg.UUIDToNetaddr(uuid.New())
|
||||
wgn, err := peerwg.New(
|
||||
slog.Make(sloghuman.Sink(cmd.ErrOrStderr())),
|
||||
[]netaddr.IPPrefix{netaddr.IPPrefixFrom(ipv6, 128)},
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create wireguard network: %w", err)
|
||||
}
|
||||
defer wgn.Close()
|
||||
|
||||
err = client.PostWireguardPeer(ctx, workspace.ID, peerwg.Handshake{
|
||||
Recipient: workspaceAgent.ID,
|
||||
NodePublicKey: wgn.NodePrivateKey.Public(),
|
||||
DiscoPublicKey: wgn.DiscoPublicKey,
|
||||
IPv6: ipv6,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("post wireguard peer: %w", err)
|
||||
}
|
||||
|
||||
err = wgn.AddPeer(peerwg.Handshake{
|
||||
Recipient: workspaceAgent.ID,
|
||||
DiscoPublicKey: workspaceAgent.DiscoPublicKey,
|
||||
NodePublicKey: workspaceAgent.WireguardPublicKey,
|
||||
IPv6: workspaceAgent.IPv6.IP(),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("add workspace agent as peer: %w", err)
|
||||
}
|
||||
|
||||
if stdio {
|
||||
rawSSH, err := wgn.SSH(ctx, workspaceAgent.IPv6.IP())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rawSSH.Close()
|
||||
|
||||
go func() {
|
||||
_, _ = io.Copy(cmd.OutOrStdout(), rawSSH)
|
||||
}()
|
||||
_, _ = io.Copy(rawSSH, cmd.InOrStdin())
|
||||
return nil
|
||||
}
|
||||
|
||||
newSSHClient = func() (*gossh.Client, error) {
|
||||
return wgn.SSHClient(ctx, workspaceAgent.IPv6.IP())
|
||||
}
|
||||
go func() {
|
||||
_, _ = io.Copy(cmd.OutOrStdout(), rawSSH)
|
||||
}()
|
||||
_, _ = io.Copy(rawSSH, cmd.InOrStdin())
|
||||
return nil
|
||||
}
|
||||
|
||||
sshClient, err := newSSHClient()
|
||||
sshClient, err := conn.SSHClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -330,34 +276,34 @@ func getWorkspaceAndAgent(ctx context.Context, cmd *cobra.Command, client *coder
|
||||
if len(agents) == 0 {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("workspace %q has no agents", workspace.Name)
|
||||
}
|
||||
var agent codersdk.WorkspaceAgent
|
||||
var workspaceAgent codersdk.WorkspaceAgent
|
||||
if len(workspaceParts) >= 2 {
|
||||
for _, otherAgent := range agents {
|
||||
if otherAgent.Name != workspaceParts[1] {
|
||||
continue
|
||||
}
|
||||
agent = otherAgent
|
||||
workspaceAgent = otherAgent
|
||||
break
|
||||
}
|
||||
if agent.ID == uuid.Nil {
|
||||
if workspaceAgent.ID == uuid.Nil {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.Errorf("agent not found by name %q", workspaceParts[1])
|
||||
}
|
||||
}
|
||||
if agent.ID == uuid.Nil {
|
||||
if workspaceAgent.ID == uuid.Nil {
|
||||
if len(agents) > 1 {
|
||||
if !shuffle {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, xerrors.New("you must specify the name of an agent")
|
||||
}
|
||||
agent, err = cryptorand.Element(agents)
|
||||
workspaceAgent, err = cryptorand.Element(agents)
|
||||
if err != nil {
|
||||
return codersdk.Workspace{}, codersdk.WorkspaceAgent{}, err
|
||||
}
|
||||
} else {
|
||||
agent = agents[0]
|
||||
workspaceAgent = agents[0]
|
||||
}
|
||||
}
|
||||
|
||||
return workspace, agent, nil
|
||||
return workspace, workspaceAgent, nil
|
||||
}
|
||||
|
||||
// Attempt to poll workspace autostop. We write a per-workspace lockfile to
|
||||
|
||||
Reference in New Issue
Block a user