Skip to content

Commit

Permalink
Merge pull request #993 from pascalbreuninger/feat/ENG-3216-external-…
Browse files Browse the repository at this point in the history
…git-credentials

feat(cli): add globals git credential flags and forward them to grpc servers; intercept git credential requests if token and username are specified
  • Loading branch information
89luca89 authored Apr 10, 2024
2 parents 930749b + 96b4fc4 commit af535eb
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 35 deletions.
8 changes: 8 additions & 0 deletions cmd/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type GlobalFlags struct {
AgentDir string

DevPodHome string

GitUsername string
GitToken string
}

// SetGlobalFlags applies the global flags
Expand All @@ -28,6 +31,11 @@ func SetGlobalFlags(flags *flag.FlagSet) *GlobalFlags {
flags.BoolVar(&globalFlags.Debug, "debug", false, "Prints the stack trace if an error occurs")
flags.BoolVar(&globalFlags.Silent, "silent", false, "Run in silent mode and prevents any devpod log output except panics & fatals")

flags.StringVar(&globalFlags.GitUsername, "git-username", "", "The username to use for git operations")
flags.StringVar(&globalFlags.GitToken, "git-token", "", "The token to use for git operations")
_ = flags.MarkHidden("git-username")
_ = flags.MarkHidden("git-token")

flags.StringVar(&globalFlags.AgentDir, "agent-dir", "", "The data folder where agent data is stored.")
_ = flags.MarkHidden("agent-dir")
return globalFlags
Expand Down
96 changes: 94 additions & 2 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import (
"github.com/loft-sh/devpod/cmd/flags"
"github.com/loft-sh/devpod/cmd/machine"
"github.com/loft-sh/devpod/pkg/agent"
"github.com/loft-sh/devpod/pkg/agent/tunnelserver"
client2 "github.com/loft-sh/devpod/pkg/client"
"github.com/loft-sh/devpod/pkg/client/clientimplementation"
"github.com/loft-sh/devpod/pkg/config"
"github.com/loft-sh/devpod/pkg/gpg"
"github.com/loft-sh/devpod/pkg/port"
Expand Down Expand Up @@ -61,6 +63,11 @@ func NewSSHCmd(flags *flags.GlobalFlags) *cobra.Command {
Short: "Starts a new ssh session to a workspace",
RunE: func(_ *cobra.Command, args []string) error {
ctx := context.Background()

err := mergeDevPodSshOptions(cmd)
if err != nil {
return err
}
devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider)
if err != nil {
return err
Expand Down Expand Up @@ -140,6 +147,7 @@ func (cmd *SSHCmd) startProxyTunnel(
ctx,
func(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
return client.Ssh(ctx, client2.SshOptions{
User: cmd.User,
Stdin: stdin,
Stdout: stdout,
})
Expand Down Expand Up @@ -344,7 +352,7 @@ func (cmd *SSHCmd) startTunnel(ctx context.Context, devPodConfig *config.Config,

// start port-forwarding etc.
if !cmd.Proxy && cmd.StartServices {
go cmd.startServices(ctx, devPodConfig, containerClient, ideName, log)
go cmd.startServices(ctx, devPodConfig, containerClient, ideName, cmd.GitUsername, cmd.GitToken, log)
}

// start ssh
Expand Down Expand Up @@ -375,10 +383,16 @@ func (cmd *SSHCmd) startTunnel(ctx context.Context, devPodConfig *config.Config,
if cmd.Debug {
command += " --debug"
}
if cmd.User != "" && cmd.User != "root" {
if !cmd.Proxy && cmd.User != "" && cmd.User != "root" {
command = fmt.Sprintf("su -c \"%s\" '%s'", command, cmd.User)
}

// Traffic is coming in from the outside, we need to forward it to the container
if cmd.Proxy || cmd.Stdio {
if cmd.Proxy {
go cmd.startProxyServices(ctx, devPodConfig, containerClient, ideName, log)
}

return devssh.Run(ctx, containerClient, command, os.Stdin, os.Stdout, writer)
}

Expand All @@ -400,6 +414,8 @@ func (cmd *SSHCmd) startServices(
devPodConfig *config.Config,
containerClient *ssh.Client,
ideName string,
gitUsername,
gitToken string,
log log.Logger,
) {
if cmd.User != "" {
Expand All @@ -413,6 +429,8 @@ func (cmd *SSHCmd) startServices(
gitCredentials,
true,
nil,
gitUsername,
gitToken,
log,
)
if err != nil {
Expand All @@ -421,6 +439,68 @@ func (cmd *SSHCmd) startServices(
}
}

func (cmd *SSHCmd) startProxyServices(
ctx context.Context,
devPodConfig *config.Config,
containerClient *ssh.Client,
ideName string,
log log.Logger,
) {
if cmd.User == "" {
return
}

gitCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectGitCredentials) == "true"

stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
log.Debugf("Error creating stdout pipe: %v", err)
return
}
defer stdoutWriter.Close()

stdinReader, stdinWriter, err := os.Pipe()
if err != nil {
log.Debugf("Error creating stdin pipe: %v", err)
return
}
defer stdinWriter.Close()

cancelCtx, cancel := context.WithCancel(ctx)
defer cancel()
errChan := make(chan error, 1)
go func() {
defer cancel()
writer := log.ErrorStreamOnly().Writer(logrus.DebugLevel, false)
defer writer.Close()

command := fmt.Sprintf("'%s' agent container credentials-server --user '%s'", agent.ContainerDevPodHelperLocation, cmd.User)
if gitCredentials {
command += " --configure-git-helper"
}
if log.GetLevel() == logrus.DebugLevel {
command += " --debug"
}

errChan <- devssh.Run(cancelCtx, containerClient, command, stdinReader, stdoutWriter, writer)
}()

opts := []tunnelserver.Option{}
if cmd.GitUsername != "" && cmd.GitToken != "" {
opts = append(opts, tunnelserver.WithGitCredentialsOverride(cmd.GitUsername, cmd.GitToken))
}
err = tunnelserver.RunServicesServer(ctx, stdoutReader, stdinWriter, true, true, nil, log, opts...)
if err != nil {
log.Debugf("Error running proxy server: %v", err)
return
}
err = <-errChan
if err != nil {
log.Debugf("Error running credential server: %v", err)
return
}
}

// setupGPGAgent will forward a local gpg-agent into the remote container
// this works by using cmd/agent/workspace/setup_gpg
func (cmd *SSHCmd) setupGPGAgent(
Expand Down Expand Up @@ -518,3 +598,15 @@ func (cmd *SSHCmd) setupGPGAgent(

return devssh.Run(ctx, containerClient, command, nil, writer, writer)
}

func mergeDevPodSshOptions(cmd *SSHCmd) error {
_, err := clientimplementation.DecodeOptionsFromEnv(
clientimplementation.DevPodFlagsSsh,
cmd,
)
if err != nil {
return fmt.Errorf("decode up options: %w", err)
}

return nil
}
15 changes: 15 additions & 0 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
user,
ideConfig.Options,
cmd.GitUsername,
cmd.GitToken,
log,
)
case string(config.IDEGoland):
Expand Down Expand Up @@ -271,6 +273,8 @@ func (cmd *UpCmd) Run(
client,
user,
ideConfig.Options,
cmd.GitUsername,
cmd.GitToken,
log,
)
}
Expand Down Expand Up @@ -460,6 +464,8 @@ func (cmd *UpCmd) devPodUpMachine(
false,
false,
nil,
cmd.GitUsername,
cmd.GitToken,
log,
)
}
Expand All @@ -471,6 +477,7 @@ func startJupyterNotebookInBrowser(
client client2.BaseWorkspaceClient,
user string,
ideOptions map[string]config.OptionValue,
gitUsername, gitToken string,
logger log.Logger,
) error {
if forwardGpg {
Expand Down Expand Up @@ -515,6 +522,8 @@ func startJupyterNotebookInBrowser(
targetURL,
false,
extraPorts,
gitUsername,
gitToken,
logger,
)
}
Expand Down Expand Up @@ -561,6 +570,7 @@ func startVSCodeInBrowser(
client client2.BaseWorkspaceClient,
workspaceFolder, user string,
ideOptions map[string]config.OptionValue,
gitUsername, gitToken string,
logger log.Logger,
) error {
if forwardGpg {
Expand Down Expand Up @@ -606,6 +616,8 @@ func startVSCodeInBrowser(
targetURL,
forwardPorts,
extraPorts,
gitUsername,
gitToken,
logger,
)
}
Expand Down Expand Up @@ -648,6 +660,7 @@ func startBrowserTunnel(
user, targetURL string,
forwardPorts bool,
extraPorts []string,
gitUsername, gitToken string,
logger log.Logger,
) error {
err := tunnel.NewTunnel(
Expand Down Expand Up @@ -688,6 +701,8 @@ func startBrowserTunnel(
true,
true,
extraPorts,
gitUsername,
gitToken,
logger,
)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func InjectAgentAndExecute(
if version.GetVersion() == version.DevVersion {
preferDownload = false

if runtime.GOOS != "linux" {
if runtime.GOOS == "windows" {
preferDownload = true
}
}
Expand Down
54 changes: 54 additions & 0 deletions pkg/agent/tunnelserver/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package tunnelserver

import (
"github.com/loft-sh/devpod/pkg/devcontainer/config"
"github.com/loft-sh/devpod/pkg/netstat"
provider2 "github.com/loft-sh/devpod/pkg/provider"
)

type Option func(*tunnelServer) *tunnelServer

func WithWorkspace(workspace *provider2.Workspace) Option {
return func(s *tunnelServer) *tunnelServer {
s.workspace = workspace
return s
}
}

func WithForwarder(forwarder netstat.Forwarder) Option {
return func(s *tunnelServer) *tunnelServer {
s.forwarder = forwarder
return s
}
}

func WithAllowGitCredentials(allowGitCredentials bool) Option {
return func(s *tunnelServer) *tunnelServer {
s.allowGitCredentials = allowGitCredentials
return s
}
}

func WithAllowDockerCredentials(allowDockerCredentials bool) Option {
return func(s *tunnelServer) *tunnelServer {
s.allowDockerCredentials = allowDockerCredentials
return s
}
}

func WithMounts(mounts []*config.Mount) Option {
return func(s *tunnelServer) *tunnelServer {
s.mounts = mounts
return s
}
}

func WithGitCredentialsOverride(username string, token string) Option {
return func(s *tunnelServer) *tunnelServer {
s.gitCredentialsOverride = gitCredentialsOverride{
username: username,
token: token,
}
return s
}
}
31 changes: 30 additions & 1 deletion pkg/agent/tunnelserver/proxyserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import (

"github.com/loft-sh/devpod/pkg/agent/tunnel"
"github.com/loft-sh/devpod/pkg/devcontainer/config"
"github.com/loft-sh/devpod/pkg/gitcredentials"
"github.com/loft-sh/devpod/pkg/stdio"
"github.com/loft-sh/log"
perrors "github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

func RunProxyServer(ctx context.Context, client tunnel.TunnelClient, reader io.Reader, writer io.WriteCloser, log log.Logger) (*config.Result, error) {
func RunProxyServer(ctx context.Context, client tunnel.TunnelClient, reader io.Reader, writer io.WriteCloser, log log.Logger, gitUsername, gitToken string) (*config.Result, error) {
lis := stdio.NewStdioListener(reader, writer, false)
s := grpc.NewServer()
tunnelServ := &proxyServer{
client: client,
log: log,

gitUsername: gitUsername,
gitToken: gitToken,
}
tunnel.RegisterTunnelServer(s, tunnelServ)
reflection.Register(s)
Expand All @@ -42,6 +47,9 @@ type proxyServer struct {
client tunnel.TunnelClient
result *config.Result
log log.Logger

gitUsername string
gitToken string
}

func (t *proxyServer) ForwardPort(ctx context.Context, portRequest *tunnel.ForwardPortRequest) (*tunnel.ForwardPortResponse, error) {
Expand All @@ -61,6 +69,27 @@ func (t *proxyServer) GitUser(ctx context.Context, empty *tunnel.Empty) (*tunnel
}

func (t *proxyServer) GitCredentials(ctx context.Context, message *tunnel.Message) (*tunnel.Message, error) {
// if we have a git token reuse that and don't ask the user
if t.gitToken != "" {
credentials := &gitcredentials.GitCredentials{}
err := json.Unmarshal([]byte(message.Message), credentials)
if err != nil {
return nil, perrors.Wrap(err, "decode git credentials request")
}

response, err := gitcredentials.GetCredentials(credentials, t.gitUsername, t.gitToken)
if err != nil {
return nil, perrors.Wrap(err, "get git response")
}

out, err := json.Marshal(response)
if err != nil {
return nil, err
}

return &tunnel.Message{Message: string(out)}, nil
}

return t.client.GitCredentials(ctx, message)
}

Expand Down
Loading

0 comments on commit af535eb

Please sign in to comment.