Skip to content

Commit

Permalink
Merge pull request #28 from benammann/6-scan-for-plain-passwords-in-g…
Browse files Browse the repository at this point in the history
…it-hook

adds git secrets scan command
  • Loading branch information
benammann authored Aug 6, 2022
2 parents 0f7088a + b12b7b2 commit be57741
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

go test ./...
git secrets scan
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
watch:
CompileDaemon
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const FlagForce = "force"
const FlagDebug = "debug"
const FlagDryRun = "dry-run"
const FlagTarget = "target"
const FlagAll = "all"
const FlagVerbose = "verbose"

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Expand Down
200 changes: 200 additions & 0 deletions cmd/scan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
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 cmd

import (
"bufio"
"fmt"
config_generic "github.com/benammann/git-secrets/pkg/config/generic"
gitutil "github.com/benammann/git-secrets/pkg/git_utility"
"github.com/benammann/git-secrets/pkg/utility"
"github.com/fatih/color"
"os"
"strings"
"sync"
"time"

"github.com/spf13/cobra"
)

type DecodedSecret struct {
secret *config_generic.Secret
decodedValue string
}

// scanCmd represents the scan command
var scanCmd = &cobra.Command{
Use: "scan",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
cobra.CheckErr(projectCfgError)
},
Short: "A brief description of your command",
Run: func(cmd *cobra.Command, args []string) {

start := time.Now()

scanAll, _ := cmd.Flags().GetBool(FlagAll)
verbose, _ := cmd.Flags().GetBool(FlagVerbose)

stagedFiles, errStagedFiles := gitutil.GetStagedFiles(scanAll)
if errStagedFiles != nil {
cobra.CheckErr(errStagedFiles)
}

if len(stagedFiles) == 0 {
fmt.Println("no staged files found. use --all to scan all files")
return
}

var decodedSecrets []*DecodedSecret

for _, context := range projectCfg.GetContexts() {
contextSecrets := projectCfg.GetSecretsByContext(context.Name)
for _, secret := range contextSecrets {
decodedValue, errDecode := secret.Decode()
if errDecode != nil {
color.Yellow("Warning: could not decode secret %s from context %s, skipping this secret\n", secret.Name, secret.OriginContext.Name)
continue
}
decodedSecrets = append(decodedSecrets, &DecodedSecret{secret: secret, decodedValue: decodedValue})
}
}

var leakedSecrets []struct {
fileName string
line int
lineContent string
secret *DecodedSecret
}

registerLeakedSecret := func(fileName string, line int, lineContent string, secret *DecodedSecret) {

lineContent = strings.ReplaceAll(lineContent, secret.decodedValue, "************")

leakedSecrets = append(leakedSecrets, struct {
fileName string
line int
lineContent string
secret *DecodedSecret
}{fileName: fileName, line: line, lineContent: lineContent, secret: secret})
}

var failures []struct {
fileName string
err error
}

registerFailure := func(fileName string, err error) {
failures = append(failures, struct {
fileName string
err error
}{fileName: fileName, err: err})
}

stagedFilesChunks := utility.ChunkStringSlice(stagedFiles, 10000)

for _, stagedFilesChunk := range stagedFilesChunks {
var filesWaitGroup sync.WaitGroup
for _, stagedFile := range stagedFilesChunk {
filesWaitGroup.Add(1)
go func(stagedFile string) {

if verbose {
fmt.Println(stagedFile)
}

f, err := os.OpenFile(stagedFile, os.O_RDONLY, os.ModePerm)
if err != nil {
registerFailure(stagedFile, fmt.Errorf("open file error: %s", err.Error()))
return
}
defer f.Close()

sc := bufio.NewScanner(f)

currentLine := 0
for sc.Scan() {

lineContent := sc.Text()
currentLine++

for _, secret := range decodedSecrets {
if strings.Contains(lineContent, secret.decodedValue) {
registerLeakedSecret(stagedFile, currentLine, lineContent, secret)
}
}

}
if err := sc.Err(); err != nil {
registerFailure(stagedFile, fmt.Errorf("scan file error: %s", err.Error()))
return
}

filesWaitGroup.Done()
}(stagedFile)
}
filesWaitGroup.Wait()
}

elapsed := time.Since(start)

yellow := color.New(color.FgYellow).SprintFunc()
red := color.New(color.FgRed).SprintFunc()

if len(leakedSecrets) > 0 || len(failures) > 0 {

if verbose {
fmt.Printf("\n")
}

for _, failure := range failures {
fmt.Printf("%s - error: %s\n", red(failure.fileName), yellow(failure.err.Error()))
}

for _, leakedSecret := range leakedSecrets {
fmt.Printf("%s:%s - secret %s from context %s is present\n", red(leakedSecret.fileName), yellow(leakedSecret.line), yellow(leakedSecret.secret.secret.Name), yellow(leakedSecret.secret.secret.OriginContext.Name))
fmt.Printf("%s%d | %s\n\n", yellow("> "), leakedSecret.line, leakedSecret.lineContent)
}

color.Red("Searched in %d files for %d secrets in %s \n", len(stagedFiles), len(decodedSecrets), elapsed)

os.Exit(1)
} else {

if verbose {
fmt.Printf("\n")
}
color.Green("All files are clean of any leaked secret contained in .git-secrets.json\n")
color.Green("Searched in %d files for %d secrets in %s \n", len(stagedFiles), len(decodedSecrets), elapsed)
}

},
}

func init() {
rootCmd.AddCommand(scanCmd)
scanCmd.Flags().BoolP(FlagAll, "a", false, "--all or -a: scan all files that are contained in the git repo")
scanCmd.Flags().BoolP(FlagVerbose, "v", false, "--verbose or -v: list the scanned files")
// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// scanCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// scanCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
Binary file added docs/img/git-secrets-scan-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 47 additions & 24 deletions docs/readme.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
# Git-Secrets

````text
________.__ __ _________ __
/ _____/|__|/ |_ / _____/ ____ ___________ _____/ |_ ______
/ \ ___| \ __\ \_____ \_/ __ \_/ ___\_ __ \_/ __ \ __\/ ___/
\ \_\ \ || | / \ ___/\ \___| | \/\ ___/| | \___ \
\______ /__||__| /_______ /\___ >\___ >__| \___ >__| /____ >
\/ \/ \/ \/ \/ \/
````

## encryption and rendering engine for git repositories

![Tag](https://img.shields.io/github/v/release/benammann/git-secrets?label=brew%20tag)
![Docker Image Version](https://img.shields.io/docker/v/benammann/git-secrets?label=docker%20image%20tag)
![Release Badge](https://github.com/benammann/git-secrets/actions/workflows/brew-release.yml/badge.svg)
![Tag](https://img.shields.io/github/v/release/benammann/git-secrets?label=release)
![Docker Image Version](https://img.shields.io/docker/v/benammann/git-secrets?label=image)
![Release Badge](https://github.com/benammann/git-secrets/actions/workflows/goreleaser.yml/badge.svg)
![Test Badge](https://github.com/benammann/git-secrets/actions/workflows/docker-release.yml/badge.svg)
![License](https://img.shields.io/github/license/benammann/git-secrets?123)

Expand All @@ -30,28 +19,44 @@ Git Secrets encrypts your passwords and configurations for multiple environments

### Demo

![](../docs/img/git-secrets-demo.gif)
![](img/git-secrets-demo.gif)

### Examples

- Encoding / Decoding: [with-binary-example](examples/with-binary-example)
- Kubernetes Secrets: [render-kubernetes-secret](examples/render-kubernetes-secret)
- Github Actions [.github/workflows/docker-release.yml](.github/workflows/docker-release.yml)


### Installation

via Homebrew / Linuxbrew
```
brew install benammann/tap/git-secrets
```
`Git-Secrets` is available on Linux, macOS and Windows platforms.

via Docker
* Binaries for Linux, Windows and Mac are available as tarballs in the [release](https://github.com/benammann/git-secrets/releases) page.

````bash
docker run benammann/git-secrets info
````

or just head over to the [Releases](https://github.com/benammann/git-secrets/releases) page and download the prebuilt binary manually
* Via Curl for Linux and Mac (uses https://github.com/jpillora/installer)

```shell
# without sudo
curl https://i.jpillora.com/benammann/git-secrets! | bash

# using sudo (if mv fails)
curl https://i.jpillora.com/benammann/git-secrets!! | bash
```

* Via Homebrew for macOS or LinuxBrew for Linux

```shell
brew install benammann/tap/git-secrets
```

* Via a GO install

```shell
# NOTE: The dev version will be in effect!
go install github.com/benammann/git-secrets@latest
```

## Getting started

Expand Down Expand Up @@ -133,6 +138,24 @@ git secrets render env --dry-run
git secrets render env -c prod
````

### Scan for plain secrets

`Git-Secrets` provides a simple command to scan for plain secrets in the project files.

![](img/git-secrets-scan-demo.png)

````bash
# scan all files added to git
git secrets scan -a

# scan staged files only
git secrets scan

# hint: add -v to show all the scanned file names
````

You should use this command to setup a pre-commit git-hook in your project. You can use Husky (https://typicode.github.io/husky/#/) to automatically install and setup the hook.


### Custom Template Functions

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
)

require (
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
"description": "",
"main": "index.js",
"scripts": {
"prepare": "husky install",
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
},
"author": "",
"license": "MIT",
"dependencies": {
"vuepress": "^2.0.0-beta.46"
},
"devDependencies": {
"husky": "^8.0.1"
}
}
Loading

0 comments on commit be57741

Please sign in to comment.