-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #234 from SAP/develop
upgrade to v4.8.0
- Loading branch information
Showing
6 changed files
with
244 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
- id: credential-digger-hook | ||
name: Credential Digger hook | ||
description: This hook run Credential Digger on pre-commited files | ||
entry: credentialdigger | ||
args: ['hook'] | ||
language: python | ||
pass_filenames: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
""" | ||
The 'hook' module can be used to run credential digger as a pre-commit hook. | ||
It detects hardcoded secrets in staged files blocking the commit before the | ||
code gets public. | ||
usage: credentialdigger hook [-h] [--dotenv DOTENV] [--sqlite SQLITE] | ||
[--rules RULES] [--no_interaction] | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
--dotenv DOTENV The path to the .env file which will be used in all | ||
commands. If not specified, the one in the current | ||
directory will be used (if present). | ||
--sqlite SQLITE If specified, scan the repo using the sqlite client | ||
passing as argument the path of the db. | ||
Otherwise, use postgres (must be up and running) | ||
--rules RULES Specify the yaml file path containing the scan rules | ||
e.g., /path/to/rules.yaml | ||
--no_interaction Flag used to remove the interaction i.e., | ||
do not prompt if the commit should continue | ||
in case of discoveries. If specified, the hook will | ||
fail in case of discoveries. | ||
""" | ||
|
||
import subprocess | ||
import sys | ||
|
||
from credentialdigger.models.model_manager import ModelManager | ||
|
||
|
||
def configure_parser(parser): | ||
""" Configure arguments for command line parser. | ||
Parameters | ||
---------- | ||
parser: `credentialdigger.cli.customParser` | ||
Command line parser | ||
""" | ||
parser.set_defaults(func=run) | ||
|
||
|
||
def system(*args, **kwargs): | ||
"""Run a command and get the result.""" | ||
kwargs.setdefault('stdout', subprocess.PIPE) | ||
proc = subprocess.Popen(args, **kwargs) | ||
out, err = proc.communicate() | ||
return out | ||
|
||
|
||
def print_msg(msg): | ||
"""Print a message to /dev/tty.""" | ||
subprocess.run(f'echo \"\n{msg}\n\" > /dev/tty', | ||
shell=True, | ||
stdout=subprocess.PIPE) | ||
|
||
|
||
def ask_commit(str_discoveries): | ||
"""Ask for the commit confirmation in case of possible leaks. | ||
Parameters | ||
---------- | ||
str_discoveries: str | ||
Discoveries formatted as a string | ||
""" | ||
|
||
msg = 'You have the following disoveries:\n\n' \ | ||
f'{str_discoveries}\nWould you like to commit anyway? (y/N)' | ||
print_msg(msg) | ||
|
||
sys.stdin = open('/dev/tty', 'r') | ||
# Create a process on /dev/tty to capture the input (commit or not) | ||
# It reads the input, saves it in userinput and echos it | ||
# subprocess.check_output return the output of the command i.e., userinpput | ||
user_input = subprocess.check_output('read -p \"\" userinput && echo ' | ||
'\"$userinput\"', | ||
shell=True, stdin=sys.stdin).rstrip() | ||
|
||
return user_input.decode('utf-8') | ||
|
||
|
||
def run(client, args): | ||
"""Run Credential Digger on staged files. | ||
Parameters | ||
---------- | ||
client: `credentialdigger.Client` | ||
Instance of the client on which to save results | ||
args: `argparse.Namespace` | ||
Arguments from command line parser. | ||
Returns | ||
------- | ||
While this function returns nothing, it gives an exit status (integer) | ||
that is equal to the number of discoveries causing the hook to fail. | ||
If it exits with a value that is equal to 0, then it means | ||
that the scan detected no leaks in the staged files, or it means, | ||
in case interaction, that the user choosed to commit even | ||
in case of leaks. If the exit value is 0 the hook is successful. | ||
""" | ||
|
||
files_status = system('git', 'diff', '--name-status', '--staged' | ||
).decode('utf-8').splitlines() | ||
files = [] | ||
for fs in files_status: | ||
stats = fs.split('\t') | ||
status = stats[0] | ||
# Check status using the first char | ||
# D = deleted files | ||
# R = renamed files | ||
if status[0] not in 'DR': | ||
# Get the name of the staged file | ||
filename = stats[1] | ||
files.append(filename) | ||
|
||
if args.rules: | ||
client.add_rules_from_file(args.rules) | ||
elif not client.get_rules(): | ||
client.add_rules_from_file('./ui/backend/rules.yml') | ||
|
||
new_discoveries = [] | ||
subprocess.run(f'echo \"\nChecking files={files} \" > /dev/tty', | ||
shell=True, | ||
stdout=subprocess.PIPE) | ||
|
||
# For optimization purposes, the PathModel and the PasswordModel are | ||
# separated, otherwise scan_path will call both models for each file | ||
# With this implementation the discoveries are accumulated and the | ||
# PasswordModel will be run only once for the password discoveries | ||
for staged_file in files: | ||
new_discoveries += client.scan_path(scan_path=staged_file, | ||
models=['PathModel'], | ||
force=True, | ||
debug=False) | ||
|
||
if not new_discoveries: | ||
print_msg('No hardcoded secrets found in your commit') | ||
sys.exit(0) | ||
|
||
rules = client.get_rules() | ||
password_rules = set([ | ||
r['id'] for r in rules if r['category'] == 'password']) | ||
password_discoveries = [] | ||
no_password_discoveries = [] | ||
for d in new_discoveries: | ||
disc = client.get_discovery(d) | ||
if disc['rule_id'] in password_rules: | ||
password_discoveries.append(disc) | ||
else: | ||
no_password_discoveries.append(disc) | ||
|
||
# Run the PasswordModel | ||
disc = [] | ||
if password_discoveries: | ||
mm = ModelManager('PasswordModel') | ||
disc = mm.launch_model_batch(password_discoveries) | ||
|
||
list_of_discoveries = [] | ||
for d in disc: | ||
if d['state'] == 'new': | ||
list_of_discoveries.append(d) | ||
|
||
# There may be also discoveries other than passwords | ||
list_of_discoveries += no_password_discoveries | ||
# If all the discoveries were false positive discoveries | ||
if not list_of_discoveries: | ||
print_msg('No hardcoded secrets found in your commit') | ||
sys.exit(0) | ||
|
||
str_discoveries = '' | ||
for d in list_of_discoveries: | ||
str_discoveries += (f'file: {d["file_name"]}\n' | ||
f'secret: {d["snippet"]}\n' | ||
f'line number: {d["line_number"]}\n' + | ||
40 * '-') | ||
|
||
if not args.no_interaction and \ | ||
ask_commit(str_discoveries).startswith(('y', 'Y')): | ||
print_msg('Committing...') | ||
sys.exit(0) | ||
sys.exit(len(list_of_discoveries)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ def requirements(): | |
|
||
setuptools.setup( | ||
name='credentialdigger', | ||
version='4.7.0', | ||
version='4.8.0', | ||
author='SAP SE', | ||
maintainer='Marco Rosa, Slim Trabelsi', | ||
maintainer_email='[email protected], [email protected]', | ||
|
@@ -29,4 +29,6 @@ def requirements(): | |
'Operating System :: OS Independent', | ||
], | ||
python_requires='>3.5, <3.10', | ||
entry_points={'console_scripts': ['credentialdigger=credentialdigger' | ||
'.__main__:main']}, | ||
) |