Skip to content

Commit

Permalink
Merge pull request #497 from hashicorp/fix-git-update
Browse files Browse the repository at this point in the history
Prevent Git Config Alteration on Git Update
  • Loading branch information
dduzgun-security authored Jun 21, 2024
2 parents 268c11c + 5b7ec5f commit 5a63fd9
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 15 deletions.
81 changes: 67 additions & 14 deletions get_git.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (g *GitGetter) Get(dst string, u *url.URL) error {
return err
}
if err == nil {
err = g.update(ctx, dst, sshKeyFile, ref, depth)
err = g.update(ctx, dst, sshKeyFile, u, ref, depth)
} else {
err = g.clone(ctx, dst, sshKeyFile, u, ref, depth)
}
Expand Down Expand Up @@ -228,28 +228,64 @@ func (g *GitGetter) clone(ctx context.Context, dst, sshKeyFile string, u *url.UR
return nil
}

func (g *GitGetter) update(ctx context.Context, dst, sshKeyFile, ref string, depth int) error {
// Determine if we're a branch. If we're NOT a branch, then we just
// switch to master prior to checking out
cmd := exec.CommandContext(ctx, "git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
func (g *GitGetter) update(ctx context.Context, dst, sshKeyFile string, u *url.URL, ref string, depth int) error {
// Remove all variations of .git directories
err := removeCaseInsensitiveGitDirectory(dst)
if err != nil {
return err
}

// Initialize the git repository
cmd := exec.CommandContext(ctx, "git", "init")
cmd.Dir = dst
err = getRunCommand(cmd)
if err != nil {
return err
}

// Add the git remote
cmd = exec.CommandContext(ctx, "git", "remote", "add", "origin", "--", u.String())
cmd.Dir = dst
err = getRunCommand(cmd)
if err != nil {
return err
}

// Fetch the remote ref
cmd = exec.CommandContext(ctx, "git", "fetch", "--tags")
cmd.Dir = dst
err = getRunCommand(cmd)
if err != nil {
return err
}

// Fetch the remote ref
cmd = exec.CommandContext(ctx, "git", "fetch", "origin", "--", ref)
cmd.Dir = dst
err = getRunCommand(cmd)
if err != nil {
return err
}

if getRunCommand(cmd) != nil {
// Not a branch, switch to default branch. This will also catch
// non-existent branches, in which case we want to switch to default
// and then checkout the proper branch later.
ref = findDefaultBranch(ctx, dst)
// Reset the branch to the fetched ref
cmd = exec.CommandContext(ctx, "git", "reset", "--hard", "FETCH_HEAD")
cmd.Dir = dst
err = getRunCommand(cmd)
if err != nil {
return err
}

// We have to be on a branch to pull
if err := g.checkout(ctx, dst, ref); err != nil {
// Checkout ref branch
err = g.checkout(ctx, dst, ref)
if err != nil {
return err
}

// Pull the latest changes from the ref branch
if depth > 0 {
cmd = exec.CommandContext(ctx, "git", "pull", "--depth", strconv.Itoa(depth), "--ff-only")
cmd = exec.CommandContext(ctx, "git", "pull", "origin", "--depth", strconv.Itoa(depth), "--ff-only", "--", ref)
} else {
cmd = exec.CommandContext(ctx, "git", "pull", "--ff-only")
cmd = exec.CommandContext(ctx, "git", "pull", "origin", "--ff-only", "--", ref)
}

cmd.Dir = dst
Expand Down Expand Up @@ -377,3 +413,20 @@ func checkGitVersion(ctx context.Context, min string) error {

return nil
}

// removeCaseInsensitiveGitDirectory removes all .git directory variations
func removeCaseInsensitiveGitDirectory(dst string) error {
files, err := os.ReadDir(dst)
if err != nil {
return fmt.Errorf("Failed to read the destination directory %s during git update", dst)
}
for _, f := range files {
if strings.EqualFold(f.Name(), ".git") && f.IsDir() {
err := os.RemoveAll(filepath.Join(dst, f.Name()))
if err != nil {
return fmt.Errorf("Failed to remove the .git directory in the destination directory %s during git update", dst)
}
}
}
return nil
}
120 changes: 119 additions & 1 deletion get_git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ func TestGitGetter_remoteWithoutMaster(t *testing.T) {
t.Fatalf("err: %s", err)
}

dst2 := tempDir(t)
// Get again should work
if err := g.Get(dst, repo.url); err != nil {
if err := g.Get(dst2, repo.url); err != nil {
t.Fatalf("err: %s", err)
}

Expand Down Expand Up @@ -866,6 +867,123 @@ func TestGitGetter_BadRemoteUrl(t *testing.T) {
}
}

func TestGitGetter_BadGitConfig(t *testing.T) {
if !testHasGit {
t.Log("git not found, skipping")
t.Skip()
}

ctx := context.Background()
g := new(GitGetter)
dst := tempDir(t)

url, err := url.Parse("https://github.com/hashicorp/go-getter")
if err != nil {
t.Fatal(err)
}

_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
t.Fatalf(err.Error())
}
if err == nil {
// Update the repository containing the bad git config.
// This should remove the bad git config file and initialize a new one.
err = g.update(ctx, dst, testGitToken, url, "main", 1)
} else {
// Clone a repository with a git config file
err = g.clone(ctx, dst, testGitToken, url, "main", 1)
if err != nil {
t.Fatalf(err.Error())
}

// Edit the git config file to simulate a bad git config
gitConfigPath := filepath.Join(dst, ".git", "config")
err = os.WriteFile(gitConfigPath, []byte("bad config"), 0600)
if err != nil {
t.Fatalf(err.Error())
}

// Update the repository containing the bad git config.
// This should remove the bad git config file and initialize a new one.
err = g.update(ctx, dst, testGitToken, url, "main", 1)
}
if err != nil {
t.Fatalf(err.Error())
}

// Check if the .git/config file contains "bad config"
gitConfigPath := filepath.Join(dst, ".git", "config")
configBytes, err := os.ReadFile(gitConfigPath)
if err != nil {
t.Fatalf(err.Error())
}
if strings.Contains(string(configBytes), "bad config") {
t.Fatalf("The .git/config file contains 'bad config'")
}
}

func TestGitGetter_BadGitDirName(t *testing.T) {
if !testHasGit {
t.Log("git not found, skipping")
t.Skip()
}

ctx := context.Background()
g := new(GitGetter)
dst := tempDir(t)

url, err := url.Parse("https://github.com/hashicorp/go-getter")
if err != nil {
t.Fatal(err)
}

_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
t.Fatalf(err.Error())
}
if err == nil {
// Remove all variations of .git directories
err = removeCaseInsensitiveGitDirectory(dst)
if err != nil {
t.Fatalf(err.Error())
}
} else {
// Clone a repository with a git directory
err = g.clone(ctx, dst, testGitToken, url, "main", 1)
if err != nil {
t.Fatalf(err.Error())
}

// Rename the .git directory to .GIT
oldPath := filepath.Join(dst, ".git")
newPath := filepath.Join(dst, ".GIT")
err = os.Rename(oldPath, newPath)
if err != nil {
t.Fatalf(err.Error())
}

// Remove all variations of .git directories
err = removeCaseInsensitiveGitDirectory(dst)
if err != nil {
t.Fatalf(err.Error())
}
}
if err != nil {
t.Fatalf(err.Error())
}

// Check if the .GIT directory exists
if _, err := os.Stat(filepath.Join(dst, ".GIT")); !os.IsNotExist(err) {
t.Fatalf(".GIT directory still exists")
}

// Check if the .git directory exists
if _, err := os.Stat(filepath.Join(dst, ".git")); !os.IsNotExist(err) {
t.Fatalf(".git directory still exists")
}
}

// gitRepo is a helper struct which controls a single temp git repo.
type gitRepo struct {
t *testing.T
Expand Down

0 comments on commit 5a63fd9

Please sign in to comment.