Skip to content

Commit

Permalink
Detector for Cloud IAM Keys (Yelp#200)
Browse files Browse the repository at this point in the history
Supports git-defenders/detect-secrets-discuss#203

IBM Cloud IAM Api Key Validation (Yelp#201)

Supports git-defenders/detect-secrets-discuss#203

Refactor IBM Cloud IAM verification for owner resolution reuse (Yelp#205)

Related to git-defenders/detect-secrets-stream#222
Supports git-defenders/detect-secrets-discuss#203
  • Loading branch information
justineyster committed Sep 9, 2020
1 parent b88ad9e commit 99fa72a
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 41 deletions.
6 changes: 6 additions & 0 deletions detect_secrets/core/usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,12 @@ class PluginOptions:
disable_help_text='Disable scanning for DB2 credentials',
is_default=True,
),
PluginDescriptor(
classname='IBMCloudIAMDetector',
disable_flag_text='--no-ibm-cloud-iam-scan',
disable_help_text='Disable scanning for IBM Cloud IAM keys',
is_default=True,
),
]

default_plugins_list = [
Expand Down
1 change: 1 addition & 0 deletions detect_secrets/plugins/common/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..gh import GHDetector # noqa: F401
from ..high_entropy_strings import Base64HighEntropyString # noqa: F401
from ..high_entropy_strings import HexHighEntropyString # noqa: F401
from ..ibm_cloud_iam import IBMCloudIAMDetector # noqa: F401
from ..keyword import KeywordDetector # noqa: F401
from ..private_key import PrivateKeyDetector # noqa: F401
from ..slack import SlackDetector # noqa: F401
Expand Down
1 change: 1 addition & 0 deletions detect_secrets/plugins/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..db2 import DB2Detector # noqa: F401
from ..high_entropy_strings import Base64HighEntropyString # noqa: F401
from ..high_entropy_strings import HexHighEntropyString # noqa: F401
from ..ibm_cloud_iam import IBMCloudIAMDetector # noqa: F401
from ..keyword import KeywordDetector # noqa: F401
from ..private_key import PrivateKeyDetector # noqa: F401
from ..slack import SlackDetector # noqa: F401
Expand Down
37 changes: 27 additions & 10 deletions detect_secrets/plugins/ibm_cloud_iam.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
from __future__ import absolute_import

import re

import requests

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


class IbmCloudIamDetector(RegexBasedDetector):
"""Scans for IBM Cloud IAM Key."""
class IBMCloudIAMDetector(RegexBasedDetector):

secret_type = 'IBM Cloud IAM Key'

# opt means optional
opt_ibm_cloud_iam = r'(?:ibm(?:_|-|)cloud(?:_|-|)iam|cloud(?:_|-|)iam|' + \
r'ibm(?:_|-|)cloud|ibm(?:_|-|)iam|ibm|iam|cloud|)'
opt_quote = r'(?:"|\'|)'
opt_dashes = r'(?:--|)'
ibm_cloud_iam = r'(?:ibm(?:_|-|)cloud(?:_|-|)iam|cloud(?:_|-|)iam|' + \
r'ibm(?:_|-|)cloud|ibm(?:_|-|)iam|ibm|iam|cloud)'
opt_dash_undrscr = r'(?:_|-|)'
opt_api = r'(?:api|)'
key_or_pass = r'(?:key|pwd|password|pass|token)'
secret = r'([a-zA-Z0-9_\-]{44}(?![a-zA-Z0-9_\-]))'
opt_space = r'(?: *)'
opt_assignment = r'(?:=|:|:=|=>|)'
secret = r'([a-zA-z0-9_\-]{44})'
denylist = [
RegexBasedDetector.assign_regex_generator(
prefix_regex=opt_ibm_cloud_iam + opt_dash_undrscr + opt_api,
secret_keyword_regex=key_or_pass,
secret_regex=secret,
re.compile(
r'{opt_quote}{opt_dashes}{ibm_cloud_iam}{opt_dash_undrscr}{opt_api}{opt_dash_undrscr}'
'{key_or_pass}{opt_quote}{opt_space}{opt_assignment}{opt_space}{opt_quote}'
'{secret}{opt_quote}'.format(
opt_quote=opt_quote,
opt_dashes=opt_dashes,
ibm_cloud_iam=ibm_cloud_iam,
opt_dash_undrscr=opt_dash_undrscr,
opt_api=opt_api,
key_or_pass=key_or_pass,
opt_space=opt_space,
opt_assignment=opt_assignment,
secret=secret,
), flags=re.IGNORECASE,
),
]

Expand Down
1 change: 1 addition & 0 deletions tests/core/usage_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_consolidates_output_basic(self):
'KeywordDetector': {},
'PrivateKeyDetector': {},
'AWSKeyDetector': {},
'IBMCloudIAMDetector': {},
'SlackDetector': {},
'StripeDetector': {},
'ArtifactoryDetector': {},
Expand Down
16 changes: 16 additions & 0 deletions tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def test_scan_string_basic_default(
BasicAuthDetector : False
DB2Detector : False
GHDetector : False
IBMCloudIAMDetector: False
PrivateKeyDetector : False
SlackDetector : False
StripeDetector : False
Expand Down Expand Up @@ -366,6 +367,9 @@ def test_old_baseline_ignored_with_update_flag(
'hex_limit': 3,
'name': 'HexHighEntropyString',
},
{
'name': 'IBMCloudIAMDetector',
},
{
'name': 'KeywordDetector',
},
Expand Down Expand Up @@ -412,6 +416,9 @@ def test_old_baseline_ignored_with_update_flag(
'hex_limit': 3,
'name': 'HexHighEntropyString',
},
{
'name': 'IBMCloudIAMDetector',
},
{
'name': 'KeywordDetector',
},
Expand Down Expand Up @@ -511,6 +518,9 @@ def test_old_baseline_ignored_with_update_flag(
{
'name': 'GHDetector',
},
{
'name': 'IBMCloudIAMDetector',
},
{
'name': 'PrivateKeyDetector',
},
Expand Down Expand Up @@ -556,6 +566,9 @@ def test_old_baseline_ignored_with_update_flag(
{
'name': 'GHDetector',
},
{
'name': 'IBMCloudIAMDetector',
},
{
'name': 'PrivateKeyDetector',
},
Expand Down Expand Up @@ -700,6 +713,9 @@ def test_scan_with_default_plugin(self):
{
'name': 'GHDetector',
},
{
'name': 'IBMCloudIAMDetector',
},
{
'name': 'PrivateKeyDetector',
},
Expand Down
57 changes: 26 additions & 31 deletions tests/plugins/ibm_cloud_iam_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import responses

from detect_secrets.core.constants import VerifiedResult
from detect_secrets.plugins.ibm_cloud_iam import IbmCloudIamDetector
from detect_secrets.plugins.ibm_cloud_iam import IBMCloudIAMDetector


CLOUD_IAM_KEY = 'abcd1234abcd1234abcd1234ABCD1234ABCD1234--__'
Expand All @@ -14,83 +14,78 @@ class TestIBMCloudIamDetector:
@pytest.mark.parametrize(
'payload, should_flag',
[
('ibm-cloud_api_key: {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_cloud_iam-key : {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('IBM-API-KEY : "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('"iam_api_key" : "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('cloud-api-key: "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('"iam-password": "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('CLOUD_IAM_API_KEY:"{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm-cloud-key:{cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_key:"{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm-cloud_api_key: {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm_cloud_iam-key : {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('IBM-API-KEY : "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('"iam_api_key" : "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('cloud-api-key: "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('"iam-password": "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('CLOUD_IAM_API_KEY:"{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm-cloud-key:{cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm_key:"{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
(
'"ibm_cloud_iam_api_key":"{cloud_iam_key}"'.format(
cloud_iam_key=CLOUD_IAM_KEY,
), True,
),
('ibm_cloud_iamapikey= {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_cloud_api_key= "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('IBMCLOUDIAMAPIKEY={cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('cloud_iam_api_key="{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_api_key := {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('"ibm-iam_key" := "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_cloud_iamapikey= {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm_cloud_api_key= "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('IBMCLOUDIAMAPIKEY={cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('cloud_iam_api_key="{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm_api_key := {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('"ibm-iam_key" := "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
(
'"ibm_cloud_iam_api_key":= "{cloud_iam_key}"'.format(
cloud_iam_key=CLOUD_IAM_KEY,
), True,
),
('ibm-cloud_api_key:={cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('"cloud_iam_api_key":="{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_iam_key:= "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_iam_key:= "{cloud_iam_key}extra"'.format(cloud_iam_key=CLOUD_IAM_KEY), False),
('ibm-cloud_api_key:={cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('"cloud_iam_api_key":="{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm_iam_key:= "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True,),
('ibm_api_key:="{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm_password = "{cloud_iam_key}"'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm-cloud-pwd = {cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('ibm-cloud-pwd = {cloud_iam_key}extra'.format(cloud_iam_key=CLOUD_IAM_KEY), False),
('ibm-cloud-pwd = shorter-version', False),
('apikey:{cloud_iam_key}'.format(cloud_iam_key=CLOUD_IAM_KEY), True),
('iam_api_key="%s" % IBM_IAM_API_KEY_ENV', False),
('CLOUD_APIKEY: "insert_key_here"', False),
('cloud-iam-key:=afakekey', False),
('fake-cloud-iam-key= "not_long_enough"', False),
],
)
def test_analyze_string_content(self, payload, should_flag):
logic = IbmCloudIamDetector()
def test_analyze_string(self, payload, should_flag):
logic = IBMCloudIAMDetector()

output = logic.analyze_string_content(payload, 1, 'mock_filename')
output = logic.analyze_string(payload, 1, 'mock_filename')
assert len(output) == (1 if should_flag else 0)
if should_flag:
assert list(output.values())[0].secret_value == CLOUD_IAM_KEY

@responses.activate
def test_verify_invalid_secret(self):
responses.add(
responses.POST, 'https://iam.cloud.ibm.com/identity/token', status=400,
)

assert IbmCloudIamDetector().verify(CLOUD_IAM_KEY) == VerifiedResult.VERIFIED_FALSE
assert IBMCloudIAMDetector().verify(CLOUD_IAM_KEY) == VerifiedResult.VERIFIED_FALSE

@responses.activate
def test_verify_valid_secret(self):
responses.add(
responses.POST, 'https://iam.cloud.ibm.com/identity/token', status=200,
)

IbmCloudIamDetector().verify(CLOUD_IAM_KEY) == VerifiedResult.VERIFIED_TRUE
IBMCloudIAMDetector().verify(CLOUD_IAM_KEY) == VerifiedResult.VERIFIED_TRUE

@responses.activate
def test_verify_invalid_secret_bytes(self):
responses.add(
responses.POST, 'https://iam.cloud.ibm.com/identity/token', status=400,
)

assert IbmCloudIamDetector().verify(CLOUD_IAM_KEY_BYTES) == VerifiedResult.VERIFIED_FALSE
assert IBMCloudIAMDetector().verify(CLOUD_IAM_KEY_BYTES) == VerifiedResult.VERIFIED_FALSE

@responses.activate
def test_verify_valid_secret_byes(self):
responses.add(
responses.POST, 'https://iam.cloud.ibm.com/identity/token', status=200,
)

IbmCloudIamDetector().verify(CLOUD_IAM_KEY_BYTES) == VerifiedResult.VERIFIED_TRUE
IBMCloudIAMDetector().verify(CLOUD_IAM_KEY_BYTES) == VerifiedResult.VERIFIED_TRUE
3 changes: 3 additions & 0 deletions tests/pre_commit_hook_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ def test_baseline_gets_updated(
'hex_limit': 3,
'name': 'HexHighEntropyString',
},
{
'name': 'IBMCloudIAMDetector',
},
{
'name': 'KeywordDetector',
'keyword_exclude': None,
Expand Down

0 comments on commit 99fa72a

Please sign in to comment.