Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
janekbaraniewski committed Jan 21, 2025
1 parent 0f1d043 commit 8013e2b
Show file tree
Hide file tree
Showing 2,568 changed files with 590,660 additions and 569 deletions.
1 change: 1 addition & 0 deletions cmd/agent/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ func NewContainerCmd(flags *flags.GlobalFlags) *cobra.Command {
containerCmd.AddCommand(NewOpenVSCodeAsyncCmd())
containerCmd.AddCommand(NewCredentialsServerCmd(flags))
containerCmd.AddCommand(NewSetupLoftPlatformAccessCmd(flags))
containerCmd.AddCommand(NewNetworkDaemonCmd(flags))
return containerCmd
}
54 changes: 54 additions & 0 deletions cmd/agent/container/network_daemon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package container

import (
"context"
"fmt"
"net"

"github.com/loft-sh/devpod/cmd/flags"
"github.com/loft-sh/devpod/pkg/tailscale"
"github.com/spf13/cobra"
)

// NetworkDaemonCmd holds the cmd flags
type NetworkDaemonCmd struct {
*flags.GlobalFlags

AccessKey string
PlatformHost string
NetworkHostname string
}

// NewDaemonCmd creates a new command
func NewNetworkDaemonCmd(flags *flags.GlobalFlags) *cobra.Command {
cmd := &NetworkDaemonCmd{
GlobalFlags: flags,
}
daemonCmd := &cobra.Command{
Use: "network-daemon",
Short: "Starts tailscale network daemon",
Args: cobra.NoArgs,
RunE: cmd.Run,
}
daemonCmd.Flags().StringVar(&cmd.AccessKey, "access-key", "", "")
daemonCmd.Flags().StringVar(&cmd.PlatformHost, "host", "", "")
daemonCmd.Flags().StringVar(&cmd.NetworkHostname, "hostname", "", "")
return daemonCmd
}

// Run runs the command logic
func (cmd *NetworkDaemonCmd) Run(_ *cobra.Command, _ []string) error {
tsNet := tailscale.NewTSNet(&tailscale.TSNetConfig{
AccessKey: cmd.AccessKey,
Host: tailscale.RemoveProtocol(cmd.PlatformHost),
Hostname: cmd.NetworkHostname,
PortHandlers: map[string]func(net.Listener){
"8022": tailscale.ReverseProxyHandler("127.0.0.1:8022"),
},
})
if err := tsNet.Start(context.TODO()); err != nil {
return fmt.Errorf("cannot start tsNet server: %w", err)
}

return nil
}
34 changes: 34 additions & 0 deletions cmd/agent/container/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type SetupContainerCmd struct {
InjectGitCredentials bool
ContainerWorkspaceInfo string
SetupInfo string
AccessKey string
PlatformHost string
NetworkHostname string
}

// NewSetupContainerCmd creates a new command
Expand All @@ -72,6 +75,8 @@ func NewSetupContainerCmd(flags *flags.GlobalFlags) *cobra.Command {
setupContainerCmd.Flags().BoolVar(&cmd.InjectGitCredentials, "inject-git-credentials", false, "If DevPod should inject git credentials during setup")
setupContainerCmd.Flags().StringVar(&cmd.ContainerWorkspaceInfo, "container-workspace-info", "", "The container workspace info")
setupContainerCmd.Flags().StringVar(&cmd.SetupInfo, "setup-info", "", "The container setup info")
setupContainerCmd.Flags().StringVar(&cmd.AccessKey, "access-key", "", "Access Key to use")
setupContainerCmd.Flags().StringVar(&cmd.NetworkHostname, "network-hostname", "", "Network hostname to use")
_ = setupContainerCmd.MarkFlagRequired("setup-info")
return setupContainerCmd
}
Expand Down Expand Up @@ -202,6 +207,35 @@ func (cmd *SetupContainerCmd) Run(ctx context.Context) error {
}
}

// start tailscale networking daemon
if (cmd.AccessKey != "" && cmd.NetworkHostname != "") || true { // FIXME
err = single.Single("network.daemon.pid", func() (*exec.Cmd, error) {
logger.Infof("Start DevPod Networking Daemon")
binaryPath, err := os.Executable()
if err != nil {
return nil, err
}

return exec.Command(binaryPath, "agent", "container", "network-daemon", "--access-key", cmd.AccessKey, "--host", "host.docker.internal:8080", "--hostname", cmd.NetworkHostname), nil
})
if err != nil {
return err
}

err = single.Single("ssh.daemon.pid", func() (*exec.Cmd, error) {
logger.Infof("Start DevPod Networking Daemon")
binaryPath, err := os.Executable()
if err != nil {
return nil, err
}

return exec.Command(binaryPath, "helper", "ssh-server"), nil
})
if err != nil {
return err
}
}

out, err := json.Marshal(setupInfo)
if err != nil {
return fmt.Errorf("marshal setup info: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/agent/container/setup_loft_platform_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ func (c *SetupLoftPlatformAccessCmd) Run(_ *cobra.Command, args []string) error
err = loftconfig.AuthDevpodCliToPlatform(loftConfig, logger)
if err != nil {
// log error but don't return to allow other CLIs to install as well
logger.Warn("unable to authenticate devpod cli: %v", err)
logger.Warnf("unable to authenticate devpod cli: %w", err)
}

err = loftconfig.AuthVClusterCliToPlatform(loftConfig, logger)
if err != nil {
// log error but don't return to allow other CLIs to install as well
logger.Warn("unable to authenticate vcluster cli: %v", err)
logger.Warnf("unable to authenticate vcluster cli: %w", err)
}

return nil
Expand Down
203 changes: 200 additions & 3 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"math"
"net"
"os"
"os/exec"
"path/filepath"
Expand All @@ -25,16 +27,19 @@ import (
"github.com/loft-sh/devpod/pkg/devcontainer"
dpFlags "github.com/loft-sh/devpod/pkg/flags"
"github.com/loft-sh/devpod/pkg/gpg"
"github.com/loft-sh/devpod/pkg/platform/client"
"github.com/loft-sh/devpod/pkg/port"
"github.com/loft-sh/devpod/pkg/provider"
devssh "github.com/loft-sh/devpod/pkg/ssh"
"github.com/loft-sh/devpod/pkg/tailscale"
"github.com/loft-sh/devpod/pkg/tunnel"
workspace2 "github.com/loft-sh/devpod/pkg/workspace"
"github.com/loft-sh/log"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
)
Expand Down Expand Up @@ -68,9 +73,10 @@ type SSHCmd struct {

Proxy bool

Command string
User string
WorkDir string
Command string
User string
WorkDir string
UseTailscale bool
}

// NewSSHCmd creates a new ssh command
Expand Down Expand Up @@ -121,6 +127,7 @@ func NewSSHCmd(f *flags.GlobalFlags) *cobra.Command {
sshCmd.Flags().BoolVar(&cmd.Stdio, "stdio", false, "If true will tunnel connection through stdout and stdin")
sshCmd.Flags().BoolVar(&cmd.StartServices, "start-services", true, "If false will not start any port-forwarding or git / docker credentials helper")
sshCmd.Flags().DurationVar(&cmd.SSHKeepAliveInterval, "ssh-keepalive-interval", 55*time.Second, "How often should keepalive request be made (55s)")
sshCmd.Flags().BoolVar(&cmd.UseTailscale, "use-tailscale", true, "")

return sshCmd
}
Expand Down Expand Up @@ -154,6 +161,10 @@ func (cmd *SSHCmd) Run(
cmd.Context = devPodConfig.DefaultContext
}

if cmd.UseTailscale {
return cmd.startTailscaleTunnel(ctx, devPodConfig, client, log)
}

// check if regular workspace client
workspaceClient, ok := client.(client2.WorkspaceClient)
if ok {
Expand Down Expand Up @@ -190,6 +201,192 @@ func (cmd *SSHCmd) startProxyTunnel(
},
)
}
func (cmd *SSHCmd) startTailscaleTunnel(
ctx context.Context,
devPodConfig *config.Config,
client client2.BaseWorkspaceClient,
log log.Logger,
) error {
log.Infof("Starting Tailscale connection")
if cmd.Provider == "" {
cmd.Provider = devPodConfig.Current().DefaultProvider
}

config, err := readConfig(cmd.Context, cmd.Provider)
if err != nil {
return err
}

osHostname, err := os.Hostname()
if err != nil {
fmt.Printf("Failed to get hostname: %v\n", err)
return err
}
osHostname = strings.ReplaceAll(osHostname, ".", "-")

network := tailscale.NewTSNet(&tailscale.TSNetConfig{
AccessKey: config.AccessKey,
Host: tailscale.RemoveProtocol(config.Host),
Hostname: fmt.Sprintf("devpod.%v.client", osHostname),
LogF: func(format string, args ...any) {}, // No-op logger
PortHandlers: map[string]func(net.Listener){},
})

// Start TSNet in a separate goroutine
startCtx, cancel := context.WithCancel(ctx)
defer cancel()
errChan := make(chan error, 1)
go func() {
err := network.Start(startCtx)
if err != nil {
errChan <- fmt.Errorf("failed to start TSNet: %w", err)
}
close(errChan)
}()
// Wait for the host to become reachable
reachableHost := "devpod.my-dummy-workspace.johndoes.example.workspace" // FIXME
waitUntilReachable(ctx, network, reachableHost, log)

Check failure on line 248 in cmd/ssh.go

View workflow job for this annotation

GitHub Actions / lint

Error return value is not checked (errcheck)

log.Infof("Host %s is reachable. Proceeding with SSH session...", reachableHost)
return cmd.StartSSHSession(ctx, network, reachableHost, cmd.User, "", devPodConfig, client, log)
}

// waitUntilReachable polls until the given host is reachable via Tailscale.
func waitUntilReachable(ctx context.Context, ts tailscale.TSNet, host string, log log.Logger) error {
const retryInterval = time.Second
const maxRetries = 60

for i := 0; i < maxRetries; i++ {
conn, err := ts.Dial(ctx, "tcp", fmt.Sprintf("%s.ts.loft:8022", host))
if err == nil {
_ = conn.Close()
return nil // Host is reachable
}
log.Infof("Host %s not reachable, retrying... (%d/%d)", host, i+1, maxRetries)

select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(retryInterval):
}
}

return fmt.Errorf("host %s not reachable after %d attempts", host, maxRetries)
}

const (
LoftPlatformConfigFileName = "loft-config.json" // TODO: move somewhere else, replace hardoced strings with usage of this const
)

func readConfig(contextName string, providerName string) (*client.Config, error) {
if contextName == "" {

Check failure on line 282 in cmd/ssh.go

View workflow job for this annotation

GitHub Actions / lint

SA9003: empty branch (staticcheck)

}
providerDir, err := provider.GetProviderDir(contextName, providerName)
if err != nil {
return nil, err
}

configPath := filepath.Join(providerDir, LoftPlatformConfigFileName)

// Check if given context and provider have Loft Platform configuration
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// If not just return empty response
return &client.Config{}, nil
}

content, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}

loftConfig := &client.Config{}
err = json.Unmarshal(content, loftConfig)
if err != nil {
return nil, err
}

return loftConfig, nil
}

const loftTSNetDomain = "ts.loft"

// StartSSHSession establishes an SSH session over tsnet.
func (cmd *SSHCmd) StartSSHSession(
ctx context.Context,
ts tailscale.TSNet,
addr, username, password string,
devPodConfig *config.Config,
workspaceClient client2.BaseWorkspaceClient,
log log.Logger,
) error {
workdir := filepath.Join("/workspaces", workspaceClient.Workspace())
if cmd.WorkDir != "" {
workdir = cmd.WorkDir
}

clientConfig := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.Password(password), // FIXME - use keys
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // FIXME
}

// Dial the SSH server through TSNet
serverAddress := fmt.Sprintf("%v.%v:8022", addr, loftTSNetDomain) // FIXME
conn, err := ts.Dial(ctx, "tcp", serverAddress)
if err != nil {
return fmt.Errorf("failed to connect to %s: %w", serverAddress, err)
}
defer conn.Close()

// Establish an SSH client connection
sshConn, channels, requests, err := ssh.NewClientConn(conn, serverAddress, clientConfig)
if err != nil {
return fmt.Errorf("failed to establish SSH connection: %w", err)
}
client := ssh.NewClient(sshConn, channels, requests)
defer client.Close()

// Start an SSH session
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("failed to create SSH session: %w", err)
}
defer session.Close()

// Configure terminal for interactive shell
fd := int(os.Stdin.Fd())
if term.IsTerminal(fd) {
oldState, err := term.MakeRaw(fd)
if err != nil {
return fmt.Errorf("failed to set terminal raw mode: %w", err)
}
defer term.Restore(fd, oldState)

Check failure on line 366 in cmd/ssh.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `term.Restore` is not checked (errcheck)

session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin

termModes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
err = session.RequestPty("xterm", 80, 24, termModes)
if err != nil {
return fmt.Errorf("failed to request PTY: %w", err)
}

err = session.Run(fmt.Sprintf("cd %s; exec $SHELL --noediting -i", workdir))
if err != nil {
return fmt.Errorf("failed to start shell: %w", err)
}
}

return nil
}

func startWait(
ctx context.Context,
Expand Down
Loading

0 comments on commit 8013e2b

Please sign in to comment.