Skip to content

Commit

Permalink
Add cloudant detector (Yelp#191)
Browse files Browse the repository at this point in the history
* 1st pass cloudant tests and detector

* cleaning debugs

* whitelisting secret false positive

* correcting lint errors

* correct line break errors

* more lint

* more lint

* more lint

* more lint

* typo

* more lint

* more lint

* PR responses
  • Loading branch information
edwarj2 authored and justineyster committed Jan 8, 2020
1 parent c935af7 commit a0b1ac9
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 171 deletions.
134 changes: 64 additions & 70 deletions detect_secrets/plugins/cloudant.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,45 @@


class CloudantDetector(RegexBasedDetector):
"""Scans for Cloudant credentials."""

secret_type = 'Cloudant Credentials'

# opt means optional
opt_quote = r'(?:"|\'|)'
opt_dashes = r'(?:--|)'
opt_dot = r'(?:\.|)'
dot = r'\.'
cl_account = r'[\w\-]+'
cl = r'(?:cloudant|cl|clou)'
cl_account = r'[0-9a-z\-\_]*'
cl = r'(cloudant|cl|clou)'
opt_dash_undrscr = r'(?:_|-|)'
opt_api = r'(?:api|)'
cl_key_or_pass = opt_api + r'(?:key|pwd|pw|password|pass|token)'
cl_pw = r'([0-9a-f]{64})'
cl_api_key = r'([a-z]{24})'
cl_key_or_pass = cl + opt_dash_undrscr + r'(?:key|pwd|pw|password|pass|token)'
opt_space = r'(?: |)'
assignment = r'(?:=|:|:=|=>)'
cl_secret = r'[0-9a-f]{64}'
colon = r'\:'
at = r'\@'
http = r'(?:https?\:\/\/)'
http = r'(?:http\:\/\/|https\:\/\/)'
cloudant_api_url = r'cloudant\.com'
denylist = [
RegexBasedDetector.assign_regex_generator(
prefix_regex=cl,
secret_keyword_regex=cl_key_or_pass,
secret_regex=cl_pw,
),
RegexBasedDetector.assign_regex_generator(
prefix_regex=cl,
secret_keyword_regex=cl_key_or_pass,
secret_regex=cl_api_key,
),
re.compile(
r'{http}{cl_account}{colon}{cl_pw}{at}{cl_account}{dot}{cloudant_api_url}'.format(
http=http,
colon=colon,
r'{cl_key_or_pass}{opt_space}{assignment}{opt_space}{opt_quote}{cl_secret}'.format(
cl_key_or_pass=cl_key_or_pass,
opt_quote=opt_quote,
cl_account=cl_account,
cl_pw=cl_pw,
at=at,
dot=dot,
cloudant_api_url=cloudant_api_url,
),
flags=re.IGNORECASE,
opt_dash_undrscr=opt_dash_undrscr,
opt_api=opt_api,
opt_space=opt_space,
assignment=assignment,
cl_secret=cl_secret,
), flags=re.IGNORECASE,
),
re.compile(
r'{http}{cl_account}{colon}{cl_api_key}{at}{cl_account}{dot}{cloudant_api_url}'.format(
r'{http}{cl_account}{colon}{cl_secret}{at}{cl_account}{dot}{cloudant_api_url}'.format(
http=http,
colon=colon,
cl_account=cl_account,
cl_api_key=cl_api_key,
cl_secret=cl_secret,
at=at,
dot=dot,
cloudant_api_url=cloudant_api_url,
Expand All @@ -62,70 +56,70 @@ class CloudantDetector(RegexBasedDetector):
),
]

def verify(self, token, content):
def verify(self, token, content, potential_secret=None):

hosts = find_account(content)
hosts = get_host(content)
if not hosts:
return VerifiedResult.UNVERIFIED

for host in hosts:
return verify_cloudant_key(host, token)
return verify_cloudant_key(host, token, potential_secret)

return VerifiedResult.VERIFIED_FALSE


def find_account(content):
def get_host(content):

# opt means optional
opt_quote = r'(?:"|\'|)'
opt_cl = r'(?:cloudant|cl|)'
opt_dash_undrscr = r'(?:_|-|)'
opt_hostname_keyword = r'(?:hostname|host|username|id|user|userid|user-id|user-name|' \
'name|user_id|user_name|uname|account)'
account = r'(\w[\w\-]*)'
opt_basic_auth = r'(?:[\w\-:%]*\@)?'

regexes = (
RegexBasedDetector.assign_regex_generator(
prefix_regex=CloudantDetector.cl,
secret_keyword_regex=opt_hostname_keyword,
secret_regex=account,
),
re.compile(
r'{http}{opt_basic_auth}{cl_account}{dot}{cloudant_api_url}'.format(
http=CloudantDetector.http,
opt_basic_auth=opt_basic_auth,
cl_account=account,
cl_api_key=CloudantDetector.cl_api_key,
dot=CloudantDetector.dot,
cloudant_api_url=CloudantDetector.cloudant_api_url,
),
flags=re.IGNORECASE,
),
'name|user_id|user_name|uname)'
opt_space = r'(?: |)'
assignment = r'(?:\=|:|:=|=>)+'
hostname = r'(\w(?:\w|_|-)+)'
regex = re.compile(
r'{opt_quote}{opt_cl}{opt_dash_undrscr}{opt_hostname_keyword}{opt_space}{opt_quote}'
'{assignment}{opt_space}{opt_quote}{hostname}{opt_quote}'.format(
opt_quote=opt_quote,
opt_cl=opt_cl,
opt_dash_undrscr=opt_dash_undrscr,
opt_hostname_keyword=opt_hostname_keyword,
opt_space=opt_space,
hostname=hostname,
assignment=assignment,
), flags=re.IGNORECASE,
)

return [
match
for line in content.splitlines()
for regex in regexes
for match in regex.findall(line)
]


def verify_cloudant_key(hostname, token):
headers = {'Content-type': 'application/json'}
request_url = 'https://{hostname}:' \
'{token}' \
'@{hostname}.' \
'cloudant.com'.format(
hostname=hostname,
token=token,
)

def verify_cloudant_key(hostname, token, potential_secret=None):
try:
headers = {'Content-type': 'application/json'}
request_url = 'https://{hostname}:' \
'{token}' \
'@{hostname}.' \
'cloudant.com/_api/v2'.format(
hostname=hostname,
token=token,
)

response = requests.get(
request_url,
headers=headers,
)
except requests.exceptions.RequestException:
return VerifiedResult.UNVERIFIED

if response.status_code == 200:
return VerifiedResult.VERIFIED_TRUE
else:
return VerifiedResult.VERIFIED_FALSE
if response.status_code == 200:
if potential_secret:
potential_secret.other_factors['hostname'] = hostname
return VerifiedResult.VERIFIED_TRUE
else:
return VerifiedResult.VERIFIED_FALSE
except Exception:
return VerifiedResult.UNVERIFIED
Loading

0 comments on commit a0b1ac9

Please sign in to comment.