code.gitea.io/gitea@v1.21.7/routers/private/hook_verification.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package private
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  
    13  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    14  	"code.gitea.io/gitea/modules/git"
    15  	"code.gitea.io/gitea/modules/log"
    16  )
    17  
    18  // This file contains commit verification functions for refs passed across in hooks
    19  
    20  func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error {
    21  	stdoutReader, stdoutWriter, err := os.Pipe()
    22  	if err != nil {
    23  		log.Error("Unable to create os.Pipe for %s", repo.Path)
    24  		return err
    25  	}
    26  	defer func() {
    27  		_ = stdoutReader.Close()
    28  		_ = stdoutWriter.Close()
    29  	}()
    30  
    31  	var command *git.Command
    32  	if oldCommitID == git.EmptySHA {
    33  		// When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all":
    34  		// List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits
    35  		// So, it only lists the new commits received, doesn't list the commits already present in the receiving repository
    36  		command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all")
    37  	} else {
    38  		command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID)
    39  	}
    40  	// This is safe as force pushes are already forbidden
    41  	err = command.Run(&git.RunOpts{
    42  		Env:    env,
    43  		Dir:    repo.Path,
    44  		Stdout: stdoutWriter,
    45  		PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
    46  			_ = stdoutWriter.Close()
    47  			err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
    48  			if err != nil {
    49  				log.Error("%v", err)
    50  				cancel()
    51  			}
    52  			_ = stdoutReader.Close()
    53  			return err
    54  		},
    55  	})
    56  	if err != nil && !isErrUnverifiedCommit(err) {
    57  		log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err)
    58  	}
    59  	return err
    60  }
    61  
    62  func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error {
    63  	scanner := bufio.NewScanner(input)
    64  	for scanner.Scan() {
    65  		line := scanner.Text()
    66  		err := readAndVerifyCommit(line, repo, env)
    67  		if err != nil {
    68  			log.Error("%v", err)
    69  			return err
    70  		}
    71  	}
    72  	return scanner.Err()
    73  }
    74  
    75  func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error {
    76  	stdoutReader, stdoutWriter, err := os.Pipe()
    77  	if err != nil {
    78  		log.Error("Unable to create pipe for %s: %v", repo.Path, err)
    79  		return err
    80  	}
    81  	defer func() {
    82  		_ = stdoutReader.Close()
    83  		_ = stdoutWriter.Close()
    84  	}()
    85  	hash := git.MustIDFromString(sha)
    86  
    87  	return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha).
    88  		Run(&git.RunOpts{
    89  			Env:    env,
    90  			Dir:    repo.Path,
    91  			Stdout: stdoutWriter,
    92  			PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
    93  				_ = stdoutWriter.Close()
    94  				commit, err := git.CommitFromReader(repo, hash, stdoutReader)
    95  				if err != nil {
    96  					return err
    97  				}
    98  				verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
    99  				if !verification.Verified {
   100  					cancel()
   101  					return &errUnverifiedCommit{
   102  						commit.ID.String(),
   103  					}
   104  				}
   105  				return nil
   106  			},
   107  		})
   108  }
   109  
   110  type errUnverifiedCommit struct {
   111  	sha string
   112  }
   113  
   114  func (e *errUnverifiedCommit) Error() string {
   115  	return fmt.Sprintf("Unverified commit: %s", e.sha)
   116  }
   117  
   118  func isErrUnverifiedCommit(err error) bool {
   119  	_, ok := err.(*errUnverifiedCommit)
   120  	return ok
   121  }