code.gitea.io/gitea@v1.22.3/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  	objectFormat, _ := repo.GetObjectFormat()
    33  	if oldCommitID == objectFormat.EmptyObjectID().String() {
    34  		// When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all":
    35  		// List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits
    36  		// So, it only lists the new commits received, doesn't list the commits already present in the receiving repository
    37  		command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all")
    38  	} else {
    39  		command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID)
    40  	}
    41  	// This is safe as force pushes are already forbidden
    42  	err = command.Run(&git.RunOpts{
    43  		Env:    env,
    44  		Dir:    repo.Path,
    45  		Stdout: stdoutWriter,
    46  		PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
    47  			_ = stdoutWriter.Close()
    48  			err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env)
    49  			if err != nil {
    50  				log.Error("readAndVerifyCommitsFromShaReader failed: %v", err)
    51  				cancel()
    52  			}
    53  			_ = stdoutReader.Close()
    54  			return err
    55  		},
    56  	})
    57  	if err != nil && !isErrUnverifiedCommit(err) {
    58  		log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err)
    59  	}
    60  	return err
    61  }
    62  
    63  func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error {
    64  	scanner := bufio.NewScanner(input)
    65  	for scanner.Scan() {
    66  		line := scanner.Text()
    67  		err := readAndVerifyCommit(line, repo, env)
    68  		if err != nil {
    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  
    86  	commitID := git.MustIDFromString(sha)
    87  
    88  	return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha).
    89  		Run(&git.RunOpts{
    90  			Env:    env,
    91  			Dir:    repo.Path,
    92  			Stdout: stdoutWriter,
    93  			PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
    94  				_ = stdoutWriter.Close()
    95  				commit, err := git.CommitFromReader(repo, commitID, stdoutReader)
    96  				if err != nil {
    97  					return err
    98  				}
    99  				verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
   100  				if !verification.Verified {
   101  					cancel()
   102  					return &errUnverifiedCommit{
   103  						commit.ID.String(),
   104  					}
   105  				}
   106  				return nil
   107  			},
   108  		})
   109  }
   110  
   111  type errUnverifiedCommit struct {
   112  	sha string
   113  }
   114  
   115  func (e *errUnverifiedCommit) Error() string {
   116  	return fmt.Sprintf("Unverified commit: %s", e.sha)
   117  }
   118  
   119  func isErrUnverifiedCommit(err error) bool {
   120  	_, ok := err.(*errUnverifiedCommit)
   121  	return ok
   122  }