// Copyright 2023 Harness, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package githook import ( "context" "fmt" "github.com/harness/gitness/errors" "github.com/harness/gitness/git" "github.com/harness/gitness/git/api" "github.com/harness/gitness/git/hook" "github.com/harness/gitness/logging" "github.com/harness/gitness/types" "github.com/gotidy/ptr" "github.com/rs/zerolog/log" ) type scanSecretsResult struct { findings []api.Finding } func (r *scanSecretsResult) HasResults() bool { return len(r.findings) > 0 } func (c *Controller) scanSecrets( ctx context.Context, rgit RestrictedGIT, repo *types.Repository, in types.GithookPreReceiveInput, output *hook.Output, ) error { glOut, err := scanSecretsInternal( ctx, rgit, repo, in, ) if err != nil { return fmt.Errorf("failed to scan for git leaks: %w", err) } if glOut.HasResults() { printScanSecretsFindings(output, glOut.findings) output.Messages = append(output.Messages, "", "") output.Error = ptr.String("Changes blocked by security scan results") } return nil } func scanSecretsInternal(ctx context.Context, rgit RestrictedGIT, repo *types.Repository, in types.GithookPreReceiveInput, ) (scanSecretsResult, error) { var latestDfltCommitSHA string res := scanSecretsResult{} for _, refUpdate := range in.RefUpdates { ctx := logging.NewContext(ctx, loggingWithRefUpdate(refUpdate)) log := log.Ctx(ctx) if refUpdate.New.String() == types.NilSHA { log.Debug().Msg("skip deleted reference") continue } // in case the branch was just created - fallback to compare against latest default branch. baseRev := refUpdate.Old.String() + "^{commit}" rev := refUpdate.New.String() + "^{commit}" //nolint:nestif if refUpdate.Old.String() == types.NilSHA { if latestDfltCommitSHA == "" { branchOut, err := rgit.GetBranch(ctx, &git.GetBranchParams{ ReadParams: git.CreateReadParams(repo), // without any custom environment BranchName: repo.DefaultBranch, }) if errors.IsNotFound(err) { return scanSecretsResult{}, nil } if err != nil { return scanSecretsResult{}, fmt.Errorf("failed to retrieve latest commit of default branch: %w", err) } latestDfltCommitSHA = branchOut.Branch.SHA.String() } baseRev = latestDfltCommitSHA log.Debug().Msgf("use latest dflt commit %s as comparison for new branch", latestDfltCommitSHA) } log.Debug().Msg("scan for gitleaks") scanSecretsOut, err := rgit.ScanSecrets(ctx, &git.ScanSecretsParams{ ReadParams: git.ReadParams{ RepoUID: repo.GitUID, AlternateObjectDirs: in.Environment.AlternateObjectDirs, }, BaseRev: baseRev, Rev: rev, }) if err != nil { return scanSecretsResult{}, fmt.Errorf("failed to detect secret leaks: %w", err) } if len(scanSecretsOut.Findings) == 0 { log.Debug().Msg("no leaks found") continue } log.Debug().Msgf("found %d leaks", len(scanSecretsOut.Findings)) res.findings = append(res.findings, scanSecretsOut.Findings...) } return res, nil }