Skip to content

Commit

Permalink
SoftLayer token verification (Yelp#172)
Browse files Browse the repository at this point in the history
* get_username function

* Adapt regex

* Add username regex

* Complete 'get_username()' function

* Softlayer verify function

* First attempt at tests

* Finished writing tests

* Address @edwarj2 PR comments

* Address PR comments 1

* Address PR comments 2

* Address PR comments 3

* Valid if status_code=200
  • Loading branch information
justineyster committed Jan 8, 2020
1 parent 34ecb4c commit 9ed6725
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 54 deletions.
66 changes: 65 additions & 1 deletion detect_secrets/plugins/softlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import re

import requests

from .base import RegexBasedDetector
from detect_secrets.core.constants import VerifiedResult


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'(?:_|-|)'
Expand Down Expand Up @@ -39,3 +42,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
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ responses
tox-pip-extensions
tox>=3.8
unidiff
responses
197 changes: 144 additions & 53 deletions tests/plugins/softlayer_test.py
Original file line number Diff line number Diff line change
@@ -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" % <softlayer_api_key>', False),
('SOFTLAYER_APIKEY: "insert_key_here"', False),
Expand All @@ -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

0 comments on commit 9ed6725

Please sign in to comment.