Skip to content

Commit ad7aebf

Browse files
authored
feat: add SSH_CONFIG_INCLUDE_PATH to support read-only ssh configs (#326)
References loft-sh#1944 Signed-off-by: Samuel K <skevetter@pm.me>
1 parent af2c1d8 commit ad7aebf

File tree

14 files changed

+95
-37
lines changed

14 files changed

+95
-37
lines changed

cmd/agent/workspace/up.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (cmd *UpCmd) Run(ctx context.Context) error {
8181

8282
tunnelClient, logger, credentialsDir, err := initWorkspace(cancelCtx, cancel, workspaceInfo, cmd.Debug, !workspaceInfo.CLIOptions.Platform.Enabled && !workspaceInfo.CLIOptions.DisableDaemon)
8383
if err != nil {
84-
err1 := clientimplementation.DeleteWorkspaceFolder(workspaceInfo.Workspace.Context, workspaceInfo.Workspace.ID, workspaceInfo.Workspace.SSHConfigPath, logger)
84+
err1 := clientimplementation.DeleteWorkspaceFolder(workspaceInfo.Workspace.Context, workspaceInfo.Workspace.ID, workspaceInfo.Workspace.SSHConfigPath, workspaceInfo.Workspace.SSHConfigIncludePath, logger)
8585
if err1 != nil {
8686
return fmt.Errorf("%s %w", err1.Error(), err)
8787
}

cmd/build.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command {
8888
cmd.DevContainerImage,
8989
cmd.DevContainerPath,
9090
sshConfigPath,
91+
"",
9192
nil,
9293
cmd.UID,
9394
false,

cmd/pro/delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func cleanupLocalWorkspaces(ctx context.Context, devPodConfig *config.Config, wo
143143
return
144144
}
145145
// delete workspace folder
146-
err = clientimplementation.DeleteWorkspaceFolder(devPodConfig.DefaultContext, client.Workspace(), client.WorkspaceConfig().SSHConfigPath, log)
146+
err = clientimplementation.DeleteWorkspaceFolder(devPodConfig.DefaultContext, client.Workspace(), client.WorkspaceConfig().SSHConfigPath, client.WorkspaceConfig().SSHConfigIncludePath, log)
147147
if err != nil {
148148
log.WithFields(logrus.Fields{
149149
"workspaceId": w.ID,

cmd/ssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func (cmd *SSHCmd) Run(
128128
// get user
129129
if cmd.User == "" {
130130
var err error
131-
cmd.User, err = devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath)
131+
cmd.User, err = devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath, client.WorkspaceConfig().SSHConfigIncludePath)
132132
if err != nil {
133133
return err
134134
}

cmd/up.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,9 @@ func (cmd *UpCmd) Run(
213213
devPodHome = envDevPodHome
214214
}
215215
setupGPGAgentForwarding := cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true"
216+
sshConfigIncludePath := devPodConfig.ContextOption(config.ContextOptionSSHConfigIncludePath)
216217

217-
err = configureSSH(client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
218+
err = configureSSH(client, cmd.SSHConfigPath, sshConfigIncludePath, user, workdir, setupGPGAgentForwarding, devPodHome)
218219
if err != nil {
219220
return err
220221
}
@@ -850,7 +851,7 @@ func setupBackhaul(client client2.BaseWorkspaceClient, authSockId string, log lo
850851
return err
851852
}
852853

853-
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath)
854+
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath, client.WorkspaceConfig().SSHConfigIncludePath)
854855
if err != nil {
855856
remoteUser = "root"
856857
}
@@ -1002,15 +1003,24 @@ func startBrowserTunnel(
10021003
return nil
10031004
}
10041005

1005-
func configureSSH(client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) error {
1006+
func configureSSH(client client2.BaseWorkspaceClient, sshConfigPath, sshConfigIncludePath, user, workdir string, gpgagent bool, devPodHome string) error {
10061007
path, err := devssh.ResolveSSHConfigPath(sshConfigPath)
10071008
if err != nil {
10081009
return fmt.Errorf("invalid ssh config path %w", err)
10091010
}
10101011
sshConfigPath = path
10111012

1013+
if sshConfigIncludePath != "" {
1014+
includePath, err := devssh.ResolveSSHConfigPath(sshConfigIncludePath)
1015+
if err != nil {
1016+
return fmt.Errorf("invalid ssh config include path %w", err)
1017+
}
1018+
sshConfigIncludePath = includePath
1019+
}
1020+
10121021
err = devssh.ConfigureSSHConfig(
10131022
sshConfigPath,
1023+
sshConfigIncludePath,
10141024
client.Context(),
10151025
client.Workspace(),
10161026
user,
@@ -1195,7 +1205,7 @@ func buildDotCmd(devPodConfig *config.Config, dotfilesRepo, dotfilesScript strin
11951205
sshCmd = append(sshCmd, "--send-env", envKey)
11961206
}
11971207

1198-
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath)
1208+
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath, client.WorkspaceConfig().SSHConfigIncludePath)
11991209
if err != nil {
12001210
remoteUser = "root"
12011211
}
@@ -1254,7 +1264,7 @@ func setupGitSSHSignature(signingKey string, client client2.BaseWorkspaceClient,
12541264
return err
12551265
}
12561266

1257-
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath)
1267+
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath, client.WorkspaceConfig().SSHConfigIncludePath)
12581268
if err != nil {
12591269
remoteUser = "root"
12601270
}
@@ -1288,7 +1298,7 @@ func performGpgForwarding(
12881298
return err
12891299
}
12901300

1291-
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath)
1301+
remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath, client.WorkspaceConfig().SSHConfigIncludePath)
12921302
if err != nil {
12931303
remoteUser = "root"
12941304
}
@@ -1435,6 +1445,7 @@ func (cmd *UpCmd) prepareClient(ctx context.Context, devPodConfig *config.Config
14351445
if cmd.SSHConfigPath == "" {
14361446
cmd.SSHConfigPath = devPodConfig.ContextOption(config.ContextOptionSSHConfigPath)
14371447
}
1448+
sshConfigIncludePath := devPodConfig.ContextOption(config.ContextOptionSSHConfigIncludePath)
14381449

14391450
client, err := workspace2.Resolve(
14401451
ctx,
@@ -1449,6 +1460,7 @@ func (cmd *UpCmd) prepareClient(ctx context.Context, devPodConfig *config.Config
14491460
cmd.DevContainerImage,
14501461
cmd.DevContainerPath,
14511462
cmd.SSHConfigPath,
1463+
sshConfigIncludePath,
14521464
source,
14531465
cmd.UID,
14541466
true,

pkg/client/clientimplementation/daemonclient/delete.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (c *client) Delete(ctx context.Context, opt clientpkg.DeleteOptions) error
2727
return err
2828
} else if workspace == nil {
2929
// delete the workspace folder
30-
err = clientimplementation.DeleteWorkspaceFolder(c.workspace.Context, c.workspace.ID, c.workspace.SSHConfigPath, c.log)
30+
err = clientimplementation.DeleteWorkspaceFolder(c.workspace.Context, c.workspace.ID, c.workspace.SSHConfigPath, c.workspace.SSHConfigIncludePath, c.log)
3131
if err != nil {
3232
return err
3333
}
@@ -67,7 +67,7 @@ func (c *client) Delete(ctx context.Context, opt clientpkg.DeleteOptions) error
6767
}
6868

6969
// delete the workspace folder
70-
err = clientimplementation.DeleteWorkspaceFolder(c.workspace.Context, c.workspace.ID, c.workspace.SSHConfigPath, c.log)
70+
err = clientimplementation.DeleteWorkspaceFolder(c.workspace.Context, c.workspace.ID, c.workspace.SSHConfigPath, c.workspace.SSHConfigIncludePath, c.log)
7171
if err != nil {
7272
return err
7373
}

pkg/client/clientimplementation/proxy_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ func (s *proxyClient) Delete(ctx context.Context, opt client.DeleteOptions) erro
317317
s.log.Errorf("Error deleting workspace: %v", err)
318318
}
319319

320-
return DeleteWorkspaceFolder(s.workspace.Context, s.workspace.ID, s.workspace.SSHConfigPath, s.log)
320+
return DeleteWorkspaceFolder(s.workspace.Context, s.workspace.ID, s.workspace.SSHConfigPath, s.workspace.SSHConfigIncludePath, s.log)
321321
}
322322

323323
func (s *proxyClient) Stop(ctx context.Context, opt client.StopOptions) error {

pkg/client/clientimplementation/workspace_client.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ func (s *workspaceClient) Delete(ctx context.Context, opt client.DeleteOptions)
410410
}
411411
}
412412

413-
return DeleteWorkspaceFolder(s.workspace.Context, s.workspace.ID, s.workspace.SSHConfigPath, s.log)
413+
return DeleteWorkspaceFolder(s.workspace.Context, s.workspace.ID, s.workspace.SSHConfigPath, s.workspace.SSHConfigIncludePath, s.log)
414414
}
415415

416416
func (s *workspaceClient) isMachineRunning(ctx context.Context) (bool, error) {
@@ -643,14 +643,22 @@ func DeleteMachineFolder(context, machineID string) error {
643643
return nil
644644
}
645645

646-
func DeleteWorkspaceFolder(context string, workspaceID string, sshConfigPath string, log log.Logger) error {
646+
func DeleteWorkspaceFolder(context string, workspaceID string, sshConfigPath string, sshConfigIncludePath string, log log.Logger) error {
647647
path, err := ssh.ResolveSSHConfigPath(sshConfigPath)
648648
if err != nil {
649649
return err
650650
}
651651
sshConfigPath = path
652652

653-
err = ssh.RemoveFromConfig(workspaceID, sshConfigPath, log)
653+
if sshConfigIncludePath != "" {
654+
includePath, err := ssh.ResolveSSHConfigPath(sshConfigIncludePath)
655+
if err != nil {
656+
return err
657+
}
658+
sshConfigIncludePath = includePath
659+
}
660+
661+
err = ssh.RemoveFromConfig(workspaceID, sshConfigPath, sshConfigIncludePath, log)
654662
if err != nil {
655663
log.Errorf("Remove workspace '%s' from ssh config: %v", workspaceID, err)
656664
}

pkg/config/context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const (
1919
ContextOptionDotfilesScript = "DOTFILES_SCRIPT"
2020
ContextOptionSSHAgentForwarding = "SSH_AGENT_FORWARDING"
2121
ContextOptionSSHConfigPath = "SSH_CONFIG_PATH"
22+
ContextOptionSSHConfigIncludePath = "SSH_CONFIG_INCLUDE_PATH"
2223
ContextOptionAgentInjectTimeout = "AGENT_INJECT_TIMEOUT"
2324
ContextOptionRegistryCache = "REGISTRY_CACHE"
2425
ContextOptionSSHStrictHostKeyChecking = "SSH_STRICT_HOST_KEY_CHECKING"
@@ -89,6 +90,10 @@ var ContextOptions = []ContextOption{
8990
Name: ContextOptionSSHConfigPath,
9091
Description: "Specifies the path where the ssh config should be written to",
9192
},
93+
{
94+
Name: ContextOptionSSHConfigIncludePath,
95+
Description: "Specifies an alternate path where DevPod host entries should be written. Use this when your main SSH config is read-only (e.g., managed by Nix). Your main SSH config should have an Include directive pointing to this file.",
96+
},
9297
{
9398
Name: ContextOptionAgentInjectTimeout,
9499
Description: "Specifies the timeout to inject the agent",

pkg/provider/workspace.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type Workspace struct {
7070

7171
// Path to the file where the SSH config to access the workspace is stored
7272
SSHConfigPath string `json:"sshConfigPath,omitempty"`
73+
74+
// Path to an alternate file where DevPod entries are written (for read-only SSH configs)
75+
SSHConfigIncludePath string `json:"sshConfigIncludePath,omitempty"`
7376
}
7477

7578
type ProMetadata struct {

0 commit comments

Comments
 (0)