diff --git a/detect_secrets/plugins/softlayer.py b/detect_secrets/plugins/softlayer.py index 4ac8e1aeb..7c906da9a 100644 --- a/detect_secrets/plugins/softlayer.py +++ b/detect_secrets/plugins/softlayer.py @@ -1,6 +1,9 @@ import re +import requests + from .base import RegexBasedDetector +from detect_secrets.core.constants import VerifiedResult class SoftLayerDetector(RegexBasedDetector): @@ -8,7 +11,7 @@ class SoftLayerDetector(RegexBasedDetector): secret_type = 'SoftLayer Credentials' # opt means optional - opt_quote = r'(?:"|)' + opt_quote = r'(?:"|\'|)' opt_dashes = r'(?:--|)' sl = r'(?:softlayer|sl)' opt_dash_undrscr = r'(?:_|-|)' @@ -37,3 +40,64 @@ class SoftLayerDetector(RegexBasedDetector): flags=re.IGNORECASE, ), ] + + def verify(self, token, content): + usernames = get_username(content) + if not usernames: + return VerifiedResult.UNVERIFIED + + for username in usernames: + return verify_softlayer_key(username, token) + + return VerifiedResult.VERIFIED_FALSE + + +def get_username(content): + # opt means optional + opt_quote = r'(?:"|\'|)' + opt_dashes = r'(?:--|)' + opt_sl = r'(?:softlayer|sl|)' + opt_dash_undrscr = r'(?:_|-|)' + opt_api = r'(?:api|)' + username_keyword = r'(?:username|id|user|userid|user-id|user-name|name|user_id|user_name|uname)' + opt_space = r'(?: |)' + opt_equals = r'(?:=|:|:=|=>|)' + seperator = r'(?: |=|:|:=|=>)+' + username = r'(\w(?:\w|_|@|\.|-)+)' + regex = re.compile( + r'{opt_quote}{opt_dashes}{opt_sl}{opt_dash_undrscr}{opt_api}{opt_dash_undrscr}' + '{username_keyword}{opt_quote}{seperator}{opt_quote}{username}{opt_quote}'.format( + opt_quote=opt_quote, + opt_dashes=opt_dashes, + opt_sl=opt_sl, + opt_dash_undrscr=opt_dash_undrscr, + opt_api=opt_api, + username_keyword=username_keyword, + opt_space=opt_space, + opt_equals=opt_equals, + username=username, + seperator=seperator, + ), flags=re.IGNORECASE, + ) + + return [ + match + for line in content.splitlines() + for match in regex.findall(line) + ] + + +def verify_softlayer_key(username, token): + try: + headers = {'Content-type': 'application/json'} + response = requests.get( + 'https://api.softlayer.com/rest/v3/SoftLayer_Account.json', + auth=(username, token), headers=headers, + ) + + if response.status_code == 200: + return VerifiedResult.VERIFIED_TRUE + else: + return VerifiedResult.VERIFIED_FALSE + except Exception: + return VerifiedResult.UNVERIFIED diff --git a/requirements-dev.txt b/requirements-dev.txt index 34706eeca..fb5276b4d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,3 +12,4 @@ responses tox-pip-extensions tox>=3.8 unidiff +responses diff --git a/tests/plugins/softlayer_test.py b/tests/plugins/softlayer_test.py index 2798e3816..184d0040b 100644 --- a/tests/plugins/softlayer_test.py +++ b/tests/plugins/softlayer_test.py @@ -1,68 +1,74 @@ from __future__ import absolute_import +import textwrap + import pytest +import responses +from detect_secrets.core.constants import VerifiedResult +from detect_secrets.plugins.softlayer import get_username from detect_secrets.plugins.softlayer import SoftLayerDetector +SL_USERNAME = 'test@testy.test' +SL_TOKEN = 'abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234' -class TestSoftLayerDetector(object): - sl_token = 'abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234' +class TestSoftLayerDetector(object): @pytest.mark.parametrize( 'payload, should_flag', [ - ('--softlayer-api-key "{sl_token}"'.format(sl_token=sl_token), True,), - ('--softlayer-api-key="{sl_token}"'.format(sl_token=sl_token), True,), - ('--softlayer-api-key {sl_token}'.format(sl_token=sl_token), True,), - ('--softlayer-api-key={sl_token}'.format(sl_token=sl_token), True,), - ('http://api.softlayer.com/soap/v3/{sl_token}'.format(sl_token=sl_token), True,), - ('http://api.softlayer.com/soap/v3.1/{sl_token}'.format(sl_token=sl_token), True,), - ('softlayer_api_key: {sl_token}'.format(sl_token=sl_token), True,), - ('softlayer-key : {sl_token}'.format(sl_token=sl_token), True,), - ('SOFTLAYER-API-KEY : "{sl_token}"'.format(sl_token=sl_token), True,), - ('"softlayer_api_key" : "{sl_token}"'.format(sl_token=sl_token), True,), - ('softlayer-api-key: "{sl_token}"'.format(sl_token=sl_token), True,), - ('"softlayer_api_key": "{sl_token}"'.format(sl_token=sl_token), True,), - ('SOFTLAYER_API_KEY:"{sl_token}"'.format(sl_token=sl_token), True,), - ('softlayer-key:{sl_token}'.format(sl_token=sl_token), True,), - ('softlayer_key:"{sl_token}"'.format(sl_token=sl_token), True,), - ('"softlayer_api_key":"{sl_token}"'.format(sl_token=sl_token), True,), - ('softlayerapikey= {sl_token}'.format(sl_token=sl_token), True,), - ('softlayer_api_key= "{sl_token}"'.format(sl_token=sl_token), True,), - ('SOFTLAYERAPIKEY={sl_token}'.format(sl_token=sl_token), True,), - ('softlayer_api_key="{sl_token}"'.format(sl_token=sl_token), True,), - ('sl_api_key: {sl_token}'.format(sl_token=sl_token), True,), - ('SLAPIKEY : {sl_token}'.format(sl_token=sl_token), True,), - ('sl_apikey : "{sl_token}"'.format(sl_token=sl_token), True,), - ('"sl_api_key" : "{sl_token}"'.format(sl_token=sl_token), True,), - ('sl-key: "{sl_token}"'.format(sl_token=sl_token), True,), - ('"sl_api_key": "{sl_token}"'.format(sl_token=sl_token), True,), - ('sl_api_key:"{sl_token}"'.format(sl_token=sl_token), True,), - ('sl_api_key:{sl_token}'.format(sl_token=sl_token), True,), - ('sl-api-key:"{sl_token}"'.format(sl_token=sl_token), True,), - ('"sl_api_key":"{sl_token}"'.format(sl_token=sl_token), True,), - ('sl_key= {sl_token}'.format(sl_token=sl_token), True,), - ('sl_api_key= "{sl_token}"'.format(sl_token=sl_token), True,), - ('sl-api-key={sl_token}'.format(sl_token=sl_token), True,), - ('slapi_key="{sl_token}"'.format(sl_token=sl_token), True,), - ('slapikey:= {sl_token}'.format(sl_token=sl_token), True,), - ('softlayer_api_key := {sl_token}'.format(sl_token=sl_token), True,), - ('sl_api_key := "{sl_token}"'.format(sl_token=sl_token), True,), - ('"softlayer_key" := "{sl_token}"'.format(sl_token=sl_token), True,), - ('sl_api_key: "{sl_token}"'.format(sl_token=sl_token), True,), - ('"softlayer_api_key":= "{sl_token}"'.format(sl_token=sl_token), True,), - ('sl-api-key:="{sl_token}"'.format(sl_token=sl_token), True,), - ('softlayer_api_key:={sl_token}'.format(sl_token=sl_token), True,), - ('slapikey:"{sl_token}"'.format(sl_token=sl_token), True,), - ('"softlayer_api_key":="{sl_token}"'.format(sl_token=sl_token), True,), - ('sl-api-key:= {sl_token}'.format(sl_token=sl_token), True,), - ('softlayer_key:= "{sl_token}"'.format(sl_token=sl_token), True,), - ('sl_api_key={sl_token}'.format(sl_token=sl_token), True), - ('softlayer_api_key:="{sl_token}"'.format(sl_token=sl_token), True), - ('softlayer_password = "{sl_token}"'.format(sl_token=sl_token), True), - ('sl_pass="{sl_token}"'.format(sl_token=sl_token), True), - ('softlayer-pwd = {sl_token}'.format(sl_token=sl_token), True), + ('--softlayer-api-key "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('--softlayer-api-key="{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('--softlayer-api-key {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('--softlayer-api-key={sl_token}'.format(sl_token=SL_TOKEN), True,), + ('http://api.softlayer.com/soap/v3/{sl_token}'.format(sl_token=SL_TOKEN), True,), + ('http://api.softlayer.com/soap/v3.1/{sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer_api_key: {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer-key : {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('SOFTLAYER-API-KEY : "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"softlayer_api_key" : "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('softlayer-api-key: "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"softlayer_api_key": "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('SOFTLAYER_API_KEY:"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('softlayer-key:{sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer_key:"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"softlayer_api_key":"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('softlayerapikey= {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer_api_key= "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('SOFTLAYERAPIKEY={sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer_api_key="{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key: {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('SLAPIKEY : {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('sl_apikey : "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"sl_api_key" : "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl-key: "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"sl_api_key": "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key:"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key:{sl_token}'.format(sl_token=SL_TOKEN), True,), + ('sl-api-key:"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"sl_api_key":"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl_key= {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key= "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl-api-key={sl_token}'.format(sl_token=SL_TOKEN), True,), + ('slapi_key="{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('slapikey:= {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer_api_key := {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key := "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"softlayer_key" := "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key: "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"softlayer_api_key":= "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl-api-key:="{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('softlayer_api_key:={sl_token}'.format(sl_token=SL_TOKEN), True,), + ('slapikey:"{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('"softlayer_api_key":="{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl-api-key:= {sl_token}'.format(sl_token=SL_TOKEN), True,), + ('softlayer_key:= "{sl_token}"'.format(sl_token=SL_TOKEN), True,), + ('sl_api_key={sl_token}'.format(sl_token=SL_TOKEN), True), + ('softlayer_api_key:="{sl_token}"'.format(sl_token=SL_TOKEN), True), + ('softlayer_password = "{sl_token}"'.format(sl_token=SL_TOKEN), True), + ('sl_pass="{sl_token}"'.format(sl_token=SL_TOKEN), True), + ('softlayer-pwd = {sl_token}'.format(sl_token=SL_TOKEN), True), ('softlayer_api_key="%s" % SL_API_KEY_ENV', False), ('sl_api_key: "%s" % ', False), ('SOFTLAYER_APIKEY: "insert_key_here"', False), @@ -76,3 +82,88 @@ def test_analyze_string(self, payload, should_flag): output = logic.analyze_string(payload, 1, 'mock_filename') assert len(output) == (1 if should_flag else 0) + + @responses.activate + def test_verify_invalid_secret(self): + responses.add( + responses.GET, 'https://api.softlayer.com/rest/v3/SoftLayer_Account.json', + json={'error': 'Access denied. '}, status=401, + ) + + assert SoftLayerDetector().verify( + SL_TOKEN, + 'softlayer_username={}'.format(SL_USERNAME), + ) == VerifiedResult.VERIFIED_FALSE + + @responses.activate + def test_verify_valid_secret(self): + responses.add( + responses.GET, 'https://api.softlayer.com/rest/v3/SoftLayer_Account.json', + json={'id': 1}, status=200, + ) + + assert SoftLayerDetector().verify( + SL_TOKEN, + 'softlayer_username={}'.format(SL_USERNAME), + ) == VerifiedResult.VERIFIED_TRUE + + @responses.activate + def test_verify_unverified_secret(self): + assert SoftLayerDetector().verify( + SL_TOKEN, + 'softlayer_username={}'.format(SL_USERNAME), + ) == VerifiedResult.UNVERIFIED + + def test_verify_no_secret(self): + assert SoftLayerDetector().verify( + SL_TOKEN, + 'no_un={}'.format(SL_USERNAME), + ) == VerifiedResult.UNVERIFIED + + +@pytest.mark.parametrize( + 'content, expected_output', + ( + ( + textwrap.dedent(""" + --softlayer-username = {} + """)[1:-1].format( + SL_USERNAME, + ), + [SL_USERNAME], + ), + + # With quotes + ( + textwrap.dedent(""" + sl_user_id = "{}" + """)[1:-1].format( + SL_USERNAME, + ), + [SL_USERNAME], + ), + + # multiple candidates + ( + textwrap.dedent(""" + softlayer_id = '{}' + sl-user = '{}' + SOFTLAYER_USERID = '{}' + softlayer-uname: {} + """)[1:-1].format( + SL_USERNAME, + 'test2@testy.test', + 'test3@testy.testy', + 'notanemail', + ), + [ + SL_USERNAME, + 'test2@testy.test', + 'test3@testy.testy', + 'notanemail', + ], + ), + ), +) +def test_get_username(content, expected_output): + assert get_username(content) == expected_output