Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions cmd/internal/logs_daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"time"

"al.essio.dev/pkg/shellescape"
"github.com/devsy-org/devsy/cmd/flags"
"github.com/devsy-org/devsy/pkg/client"
"github.com/devsy-org/devsy/pkg/config"
Expand Down Expand Up @@ -69,13 +70,13 @@ func (cmd *LogsDaemonCmd) Run(ctx context.Context, args []string) error {
}

command := fmt.Sprintf(
"%q internal agent workspace logs-daemon --context %q --id %q",
workspaceClient.AgentPath(),
workspaceClient.Context(),
workspaceClient.Workspace(),
"%s internal agent workspace logs-daemon --context %s --id %s",
shellescape.Quote(workspaceClient.AgentPath()),
shellescape.Quote(workspaceClient.Context()),
shellescape.Quote(workspaceClient.Workspace()),
)
if agentInfo.Agent.DataPath != "" {
command += fmt.Sprintf(" --agent-dir %q", agentInfo.Agent.DataPath)
command += " --agent-dir " + shellescape.Quote(agentInfo.Agent.DataPath)
}

var stdout bytes.Buffer
Expand Down
6 changes: 5 additions & 1 deletion cmd/machine/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ func (cmd *SSHCmd) Run(ctx context.Context, args []string) error {
},
Exec: func(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
c := strings.Builder{}
_, _ = fmt.Fprintf(&c, "'%s' internal ssh-server --stdio", machineClient.AgentPath())
_, _ = fmt.Fprintf(
&c,
"%s internal ssh-server --stdio",
shellescape.Quote(machineClient.AgentPath()),
)
if cmd.Debug {
_, _ = c.WriteString(" --debug")
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/workspace/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"time"

"al.essio.dev/pkg/shellescape"
"github.com/devsy-org/devsy/cmd/completion"
"github.com/devsy-org/devsy/cmd/flags"
"github.com/devsy-org/devsy/pkg/agent"
Expand Down Expand Up @@ -64,7 +65,7 @@ func (cmd *LogsCmd) Run(ctx context.Context, args []string) error {
return err
}

sshServerCmd := fmt.Sprintf("'%s' internal ssh-server --stdio", client.AgentPath())
sshServerCmd := shellescape.Quote(client.AgentPath()) + " internal ssh-server --stdio"
if log.DebugEnabled() {
sshServerCmd += " --debug"
}
Expand Down Expand Up @@ -167,10 +168,10 @@ func runLogsSession(stdout, stdin *os.File, client clientpkg.WorkspaceClient) er
defer func() { _ = session.Close() }()

agentCommand := fmt.Sprintf(
"%q internal agent workspace logs --context %q --id %q",
client.AgentPath(),
client.Context(),
client.Workspace(),
"%s internal agent workspace logs --context %s --id %s",
shellescape.Quote(client.AgentPath()),
shellescape.Quote(client.Context()),
shellescape.Quote(client.Workspace()),
)
if log.DebugEnabled() {
agentCommand += " --debug"
Expand Down
9 changes: 5 additions & 4 deletions cmd/workspace/up/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"os"

"al.essio.dev/pkg/shellescape"
"github.com/devsy-org/devsy/pkg/agent"
"github.com/devsy-org/devsy/pkg/agent/tunnelserver"
client2 "github.com/devsy-org/devsy/pkg/client"
Expand Down Expand Up @@ -207,15 +208,15 @@ func (cmd *UpCmd) devsyUpMachineSSH(
return nil, fmt.Errorf("get agent info: %w", err)
}

sshTunnelCmd := fmt.Sprintf("'%s' internal ssh-server --stdio", client.AgentPath())
sshTunnelCmd := shellescape.Quote(client.AgentPath()) + " internal ssh-server --stdio"
if log.DebugEnabled() {
sshTunnelCmd += " --debug" //nolint:goconst
}

agentCommand := fmt.Sprintf(
"%q internal agent workspace up --workspace-info %q",
client.AgentPath(),
workspaceInfo,
"%s internal agent workspace up --workspace-info %s",
shellescape.Quote(client.AgentPath()),
shellescape.Quote(workspaceInfo),
)
if log.DebugEnabled() {
agentCommand += " --debug"
Expand Down
13 changes: 7 additions & 6 deletions pkg/client/clientimplementation/workspace_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"sync"
"time"

"al.essio.dev/pkg/shellescape"
"github.com/devsy-org/devsy/pkg/agent"
"github.com/devsy-org/devsy/pkg/agent/tunnelserver"
"github.com/devsy-org/devsy/pkg/client"
Expand Down Expand Up @@ -721,10 +722,10 @@ func (s *workspaceClient) agentWorkspaceCommand(subcommand string) (string, erro
}

return fmt.Sprintf(
"%q internal agent workspace %s --workspace-info %q",
info.Agent.Path,
"%s internal agent workspace %s --workspace-info %s",
shellescape.Quote(info.Agent.Path),
subcommand,
compressed,
shellescape.Quote(compressed),
), nil
}

Expand Down Expand Up @@ -1118,10 +1119,10 @@ func buildAgentCommand(
agentCommand, workspaceInfo string,
) string {
command := fmt.Sprintf(
"%q internal agent workspace %s --workspace-info %q",
workspaceClient.AgentPath(),
"%s internal agent workspace %s --workspace-info %s",
shellescape.Quote(workspaceClient.AgentPath()),
agentCommand,
workspaceInfo,
shellescape.Quote(workspaceInfo),
)
if log.DebugEnabled() {
command += " --debug"
Expand Down
36 changes: 27 additions & 9 deletions pkg/tunnel/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync"
"time"

"al.essio.dev/pkg/shellescape"
"github.com/devsy-org/devsy/pkg/agent"
"github.com/devsy-org/devsy/pkg/client"
"github.com/devsy-org/devsy/pkg/config"
Expand Down Expand Up @@ -131,7 +132,7 @@ func (c *ContainerTunnel) runHostTunnel(
defer closeAndDrain()
defer log.Debugf("Tunnel to host closed")

command := fmt.Sprintf("'%s' internal ssh-server --stdio", c.client.AgentPath())
command := shellescape.Quote(c.client.AgentPath()) + " internal ssh-server --stdio"
if log.DebugEnabled() {
command += " --debug"
}
Expand Down Expand Up @@ -186,14 +187,14 @@ func (c *ContainerTunnel) updateConfig(ctx context.Context, sshClient *ssh.Clien
break
}

buf := &bytes.Buffer{}
buf := &containerTunnelOutput{}
command := fmt.Sprintf(
"%q internal agent workspace update-config --workspace-info %q",
c.client.AgentPath(),
workspaceInfo,
"%s internal agent workspace update-config --workspace-info %s",
shellescape.Quote(c.client.AgentPath()),
shellescape.Quote(workspaceInfo),
)
if agentInfo.Agent.DataPath != "" {
command += fmt.Sprintf(" --agent-dir %q", agentInfo.Agent.DataPath)
command += " --agent-dir " + shellescape.Quote(agentInfo.Agent.DataPath)
}

log.Debugf("Run command in container: %s", command)
Expand Down Expand Up @@ -228,9 +229,9 @@ func (c *ContainerTunnel) runInContainer(
defer closeAndDrain()

command := fmt.Sprintf(
"%q internal agent container-tunnel --workspace-info %q",
c.client.AgentPath(),
workspaceInfo,
"%s internal agent container-tunnel --workspace-info %s",
shellescape.Quote(c.client.AgentPath()),
shellescape.Quote(workspaceInfo),
)
if log.DebugEnabled() {
command += " --debug"
Expand Down Expand Up @@ -265,3 +266,20 @@ func (c *ContainerTunnel) runInContainer(
},
})
}

type containerTunnelOutput struct {
mu sync.Mutex
buffer bytes.Buffer
}

func (b *containerTunnelOutput) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.Write(p)
}

func (b *containerTunnelOutput) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.String()
}
188 changes: 188 additions & 0 deletions pkg/tunnel/container_host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//go:build unix

package tunnel

import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"net"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/devsy-org/devsy/pkg/client"
"github.com/devsy-org/devsy/pkg/log"
"github.com/devsy-org/devsy/pkg/provider"
"github.com/devsy-org/devsy/pkg/shell"
devssh "github.com/devsy-org/devsy/pkg/ssh"
hostssh "github.com/devsy-org/ssh"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)

const hostTunnelStdioFlag = "--stdio"

type localHostTunnelClient struct {
client.WorkspaceClient
path string
info string
dataPath string
refresh func(context.Context) error
}

func (c localHostTunnelClient) AgentPath() string { return c.path }
func (localHostTunnelClient) AgentURL() string { return "" }
func (localHostTunnelClient) AgentLocal() bool { return true }

func (c localHostTunnelClient) AgentInfo(
provider.CLIOptions,
) (string, *provider.AgentWorkspaceInfo, error) {
return c.info, &provider.AgentWorkspaceInfo{
Agent: provider.ProviderAgentConfig{DataPath: c.dataPath},
}, nil
}

func (c localHostTunnelClient) RefreshOptions(ctx context.Context, _ []string, _ bool) error {
return c.refresh(ctx)
}

func TestHostTunnelExecutesLiteralAgentPath(t *testing.T) {
Comment thread
skevetter marked this conversation as resolved.
Resolved
for _, name := range []string{"agent space", "agent'quote", "agent '\\$-\u03bb;$(false)"} {
t.Run(name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), name)
writeHostAgent(t, path, "#!/bin/sh\nprintf '%s\\000' \"$0\" \"$@\"\ncat\n")
var stdout bytes.Buffer
tunnel := NewContainerTunnel(localHostTunnelClient{path: path})
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
require.NoError(
t,
tunnel.runHostTunnel(ctx, strings.NewReader("host-input\n"), &stdout, time.Second),
)
args := []string{path, "internal", "ssh-server", hostTunnelStdioFlag}
if log.DebugEnabled() {
args = append(args, "--debug")
}
require.Equal(t, strings.Join(args, "\x00")+"\x00host-input\n", stdout.String())
})
}
}

func TestContainerTunnelPreservesLiteralHostArguments(t *testing.T) {
for _, mode := range []string{"container-tunnel", "update-config"} {
t.Run(mode, func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "agent '$HOME-\\\u03bb")
record := filepath.Join(dir, "arguments")
t.Setenv("HOST_TUNNEL_RECORD", record)
root := writeHostAgent(
t,
path,
"#!/bin/sh\nprintf '%s\\000' \"$0\" \"$@\" > \"$HOST_TUNNEL_RECORD\"\n",
)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
workspace := localHostTunnelClient{
path: path,
info: "workspace-info",
dataPath: filepath.Join(dir, "data $HOME"),
}
calls := 0
workspace.refresh = func(ctx context.Context) error {
calls++
if calls > 1 {
cancel()
}
return ctx.Err()
}
tunnel := NewContainerTunnel(workspace)
client := localHostShellSSH(t)
args := []string{path, "internal", "agent"}
if mode == "update-config" {
tunnel.updateConfigInterval = time.Millisecond
tunnel.updateConfig(ctx, client)
args = append(
args,
"workspace",
mode,
"--workspace-info",
workspace.info,
"--agent-dir",
workspace.dataPath,
)
} else {
_ = tunnel.runInContainer(ctx, client, func(context.Context, *ssh.Client) error {
t.Error("fixture does not serve an inner SSH connection")
return nil
}, nil)
args = append(args, mode, "--workspace-info", workspace.info)
if log.DebugEnabled() {
args = append(args, "--debug")
}
}
got, err := root.ReadFile("arguments")
require.NoError(t, err)
require.Equal(t, strings.Join(args, "\x00")+"\x00", string(got))
})
}
}

func writeHostAgent(t *testing.T, path, script string) *os.Root {
t.Helper()
root, err := os.OpenRoot(filepath.Dir(path))
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, root.Close()) })
name := filepath.Base(path)
require.NoError(t, root.WriteFile(name, []byte(script), 0o600))
require.NoError(t, root.Chmod(name, 0o700))
return root
}

func localHostShellSSH(t *testing.T) *ssh.Client {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
signer, err := ssh.NewSignerFromKey(key)
require.NoError(t, err)
server := &hostssh.Server{Handler: func(session hostssh.Session) {
err := shell.RunEmulatedShell(session.Context(), &shell.CommandRunner{
Command: session.RawCommand(),
Stdin: session,
Stdout: session,
Stderr: session.Stderr(),
})
if err != nil {
_ = session.Exit(1)
return
}
_ = session.Exit(0)
}}
server.AddHostKey(signer)
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
done := make(chan struct{})
go func() {
defer close(done)
_ = server.Serve(listener)
}()
t.Cleanup(func() {
_ = server.Close()
select {
case <-done:
case <-time.After(time.Second):
t.Error("host SSH fixture did not stop")
}
})
conn, err := net.DialTimeout("tcp", listener.Addr().String(), time.Second)
require.NoError(t, err)
t.Cleanup(func() { _ = conn.Close() })
client, err := devssh.ClientFromConn(conn, "fixture", nil)
require.NoError(t, err)
t.Cleanup(func() { _ = client.Close() })
return client
}
Loading
Loading