Skip to content

Commit

Permalink
Add git repo info to audit results
Browse files Browse the repository at this point in the history
  • Loading branch information
Victor Zhou committed Jul 5, 2019
1 parent 0dff6b8 commit 8856d3a
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 30 deletions.
47 changes: 33 additions & 14 deletions detect_secrets/core/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from ..plugins.common.filetype import determine_file_type
from ..plugins.common.util import get_mapping_from_secret_type_to_class_name
from ..plugins.high_entropy_strings import HighEntropyStringsPlugin
from ..util import get_git_remotes
from ..util import get_git_sha
from .baseline import merge_results
from .bidirectional_iterator import BidirectionalIterator
from .code_snippet import CodeSnippetHighlighter
Expand Down Expand Up @@ -195,25 +197,34 @@ def compare_baselines(old_baseline_filename, new_baseline_filename):
secret_iterator.step_back_on_next_iteration()


def determine_audit_results(baseline):
def determine_audit_results(baseline, baseline_path):
"""
Given a baseline which has been audited, returns
a dictionary describing the results of each plugin in the following form:
{
"plugin_name1": {
"results": {
"positive": [list of secrets with is_secret: true caught by this plugin],
"negative": [list of secrets with is_secret: false caught by this plugin],
"unknown": [list of secrets with no is_secret entry caught by this plugin]
"results": {
"plugin_name1": {
"results": {
"positive": [list of secrets with is_secret: true caught by this plugin],
"negative": [list of secrets with is_secret: false caught by this plugin],
"unknown": [list of secrets with no is_secret entry caught by this plugin]
},
"config": {configuration used for the plugin}
},
"config": {configuration used for the plugin}
...
},
"repo_info": {
"remote": "remote url",
"sha": "sha of repo checkout"
},
...
}
"""
all_secrets = _secret_generator(baseline)

audit_results = defaultdict(lambda: deepcopy(EMPTY_PLUGIN_AUDIT_RESULT))
audit_results = {
'results': defaultdict(lambda: deepcopy(EMPTY_PLUGIN_AUDIT_RESULT)),
}

secret_type_mapping = get_mapping_from_secret_type_to_class_name()

for filename, secret in all_secrets:
Expand All @@ -230,16 +241,24 @@ def determine_audit_results(baseline):

plugin_name = secret_type_mapping[secret['type']]
audit_result = AUDIT_RESULT_TO_STRING[secret.get('is_secret')]
audit_results[plugin_name]['results'][audit_result].append(secret_plaintext)
audit_results['results'][plugin_name]['results'][audit_result].append(secret_plaintext)

for plugin_config in baseline['plugins_used']:
plugin_name = plugin_config['name']
if plugin_name not in audit_results:
if plugin_name not in audit_results['results']:
continue

audit_results[plugin_name]['config'].update(plugin_config)
audit_results['results'][plugin_name]['config'].update(plugin_config)

git_repo_path = os.path.dirname(os.path.abspath(baseline_path))
git_sha = get_git_sha(git_repo_path)
git_remotes = get_git_remotes(git_repo_path)

# TODO: pull in git repo and commit info
if git_sha and git_remotes:
audit_results['repo_info'] = {
'remote': git_remotes[0],
'sha': git_sha,
}

return audit_results

Expand All @@ -250,7 +269,7 @@ def print_audit_results(baseline_filename):
print('Failed to retrieve baseline from {filename}'.format(filename=baseline_filename))
return

print(json.dumps(determine_audit_results(baseline)))
print(json.dumps(determine_audit_results(baseline, baseline_filename)))


def _get_baseline_from_file(filename): # pragma: no cover
Expand Down
109 changes: 93 additions & 16 deletions tests/core/audit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,24 @@ def mock_get_raw_secret_value(self):
) as _mock:
yield _mock

@pytest.fixture
def mock_get_git_sha(self):
with mock.patch(
'detect_secrets.core.audit.get_git_sha',
return_value=None,
autospec=True,
) as _mock:
yield _mock

@pytest.fixture
def mock_get_git_remotes(self):
with mock.patch(
'detect_secrets.core.audit.get_git_remotes',
return_value=None,
autospec=True,
) as _mock:
yield _mock

def get_audited_baseline(self, plugin_config={}, is_secret=None):
"""
Returns a baseline in dict form with 1 plugin and 1 secret.
Expand Down Expand Up @@ -519,12 +537,25 @@ def get_audited_baseline(self, plugin_config={}, is_secret=None):
return baseline_fixture

@pytest.mark.parametrize(
'plugin_config',
[
{},
{'hex_limit': 2},
],
'plugin_config', [{}, {'hex_limit': 2}],
)
def test_determine_audit_results_plugin_config(
self,
mock_get_raw_secret_value,
mock_get_git_remotes,
mock_get_git_sha,
plugin_config,
):
plaintext_secret = 'some_plaintext_secret'
mock_get_raw_secret_value.return_value = plaintext_secret
baseline = self.get_audited_baseline(plugin_config, None)

results = audit.determine_audit_results(baseline, '.secrets.baseline')

if plugin_config:
assert results['results']['HexHighEntropyString']['config'].items() \
>= plugin_config.items()

@pytest.mark.parametrize(
'is_secret, expected_audited_result',
[
Expand All @@ -533,29 +564,74 @@ def get_audited_baseline(self, plugin_config={}, is_secret=None):
(None, 'unknown'),
],
)
def test_determine_audit_results(
def test_determine_audit_results_is_secret(
self,
mock_get_raw_secret_value,
plugin_config,
mock_get_git_remotes,
mock_get_git_sha,
is_secret,
expected_audited_result,
):
plaintext_secret = 'some_plaintext_secret'
mock_get_raw_secret_value.return_value = plaintext_secret
baseline = self.get_audited_baseline(plugin_config, is_secret)
baseline = self.get_audited_baseline({}, is_secret)

results = audit.determine_audit_results(baseline)
results = audit.determine_audit_results(baseline, '.secrets.baseline')

if plugin_config:
assert results['HexHighEntropyString']['config'].items() >= plugin_config.items()

for audited_result, list_of_secrets in results['HexHighEntropyString']['results'].items():
for audited_result, list_of_secrets \
in results['results']['HexHighEntropyString']['results'].items():
if audited_result == expected_audited_result:
assert plaintext_secret in list_of_secrets
else:
assert len(list_of_secrets) == 0

def test_determine_audit_results_secret_not_found(self, mock_get_raw_secret_value):
@pytest.mark.parametrize(
'git_remotes, git_sha, expected_git_info',
[
(None, None, None),
(None, 'abc', None),
(['git.com/git.git'], None, None),
(
['git.com/git.git'],
'abc',
{'remote': 'git.com/git.git', 'sha': 'abc'},
),
(
['git.com/git.git', 'hub.com/git.git'],
'abc',
{'remote': 'git.com/git.git', 'sha': 'abc'},
),
],
)
def test_determine_audit_results_git_info(
self,
mock_get_raw_secret_value,
mock_get_git_remotes,
mock_get_git_sha,
git_remotes,
git_sha,
expected_git_info,
):
plaintext_secret = 'some_plaintext_secret'
mock_get_raw_secret_value.return_value = plaintext_secret
mock_get_git_remotes.return_value = git_remotes
mock_get_git_sha.return_value = git_sha

baseline = self.get_audited_baseline({}, True)

results = audit.determine_audit_results(baseline, '.secrets.baseline')

if expected_git_info:
assert results['repo_info'] == expected_git_info
else:
assert 'repo_info' not in results

def test_determine_audit_results_secret_not_found(
self,
mock_get_raw_secret_value,
mock_get_git_remotes,
mock_get_git_sha,
):
mock_get_raw_secret_value.side_effect = audit.SecretNotFoundOnSpecifiedLineError(1)
baseline = self.get_audited_baseline({}, True)

Expand All @@ -567,9 +643,10 @@ def test_determine_audit_results_secret_not_found(self, mock_get_raw_secret_valu
return_value=whole_plaintext_line,
autospec=True,
):
results = audit.determine_audit_results(baseline)
results = audit.determine_audit_results(baseline, '.secrets.baseline')

assert whole_plaintext_line in results['HexHighEntropyString']['results']['positive']
assert whole_plaintext_line in \
results['results']['HexHighEntropyString']['results']['positive']


class TestPrintAuditResults():
Expand Down

0 comments on commit 8856d3a

Please sign in to comment.