Skip to content

Commit

Permalink
Merge pull request #78 from CiscoSecurity/release-2.0.2
Browse files Browse the repository at this point in the history
Release 2.0.2
  • Loading branch information
oshynk authored Aug 26, 2021
2 parents 83f6059 + ba689cd commit 2f3e3b3
Show file tree
Hide file tree
Showing 13 changed files with 519 additions and 67 deletions.
11 changes: 0 additions & 11 deletions .travis.yml

This file was deleted.

8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
FROM alpine:3.14
LABEL maintainer="Ian Redden <iaredden@cisco.com>"

ENV PIP_IGNORE_INSTALLED 1

# install packages we need
RUN apk update && apk add --no-cache musl-dev openssl-dev gcc py3-configobj \
supervisor git libffi-dev uwsgi-python3 uwsgi-http jq syslog-ng uwsgi-syslog \
supervisor libffi-dev uwsgi-python3 uwsgi-http jq syslog-ng uwsgi-syslog \
py3-pip python3-dev

# do the Python dependencies
ADD code /app
RUN pip3 install -r /app/requirements.txt
ADD code/Pipfile code/Pipfile.lock /
RUN set -ex && pip install --no-cache-dir --upgrade pipenv && \
pipenv install --system
RUN chown -R uwsgi.uwsgi /etc/uwsgi

# copy over scripts to init
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@ The code is provided here purely for educational purposes.

## Testing (Optional)

If you want to test the application you will require Docker and several dependencies from the [requirements.txt](code/requirements.txt) file:
Open the code folder in your terminal.
```
pip install --upgrade --requirement code/requirements.txt
cd code
```

If you want to test the application you will require Docker and several dependencies from the [Pipfile](code/Pipfile) file:
```
pip install --no-cache-dir --upgrade pipenv && pipenv install --dev
```

You can perform two kinds of testing:

- Run static code analysis checking for any semantic discrepancies and [PEP 8](https://www.python.org/dev/peps/pep-0008/) compliance:

`flake8 code`
`flake8 .`

- Run the suite of unit tests and measure the code coverage:
`cd code`
`coverage run --source api/ -m pytest --verbose tests/unit/ && coverage report`

**NOTE.** If you need input data for testing purposes you can use data from the
Expand Down
20 changes: 20 additions & 0 deletions code/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
cryptography = "==3.3.2"
Flask = "==2.0.1"
marshmallow = "==3.12.1"
requests = "==2.25.1"
PyJWT = "==2.1.0"
beautifulsoup4 = "==4.9.3"

[dev-packages]
flake8 = "==3.9.2"
coverage = "==5.5"
pytest = "==6.2.4"

[requires]
python_version = "3.9"
432 changes: 432 additions & 0 deletions code/Pipfile.lock

Large diffs are not rendered by default.

70 changes: 40 additions & 30 deletions code/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import requests
from jwt import InvalidSignatureError, DecodeError, InvalidAudienceError
from flask import request, current_app, jsonify, g
from requests.exceptions import SSLError, ConnectionError, InvalidURL
from requests.exceptions import (
SSLError,
ConnectionError,
InvalidURL,
HTTPError
)
from bs4 import BeautifulSoup

from api.errors import (
Expand All @@ -22,6 +27,20 @@
AuthorizationError
)

NO_AUTH_HEADER = 'Authorization header is missing'
WRONG_AUTH_TYPE = 'Wrong authorization type'
WRONG_PAYLOAD_STRUCTURE = 'Wrong JWT payload structure'
WRONG_JWT_STRUCTURE = 'Wrong JWT structure'
WRONG_AUDIENCE = 'Wrong configuration-token-audience'
KID_NOT_FOUND = 'kid from JWT header not found in API response'
WRONG_KEY = ('Failed to decode JWT with provided key. '
'Make sure domain in custom_jwks_host '
'corresponds to your SecureX instance region.')
JWKS_HOST_MISSING = ('jwks_host is missing in JWT payload. Make sure '
'custom_jwks_host field is present in module_type')
WRONG_JWKS_HOST = ('Wrong jwks_host in JWT payload. Make sure domain follows '
'the visibility.<region>.cisco.com structure')


def url_for(endpoint) -> Optional[str]:
return current_app.config['ABUSE_IPDB_API_URL'].format(
Expand All @@ -44,8 +63,8 @@ def get_auth_token():
"""

expected_errors = {
KeyError: 'Authorization header is missing',
AssertionError: 'Wrong authorization type'
KeyError: NO_AUTH_HEADER,
AssertionError: WRONG_AUTH_TYPE,
}

try:
Expand All @@ -57,20 +76,15 @@ def get_auth_token():


def get_public_key(jwks_host, token):
expected_errors = {
ConnectionError: 'Wrong jwks_host in JWT payload. '
'Make sure domain follows the '
'visibility.<region>.cisco.com structure',
InvalidURL: 'Wrong jwks_host in JWT payload. '
'Make sure domain follows the '
'visibility.<region>.cisco.com structure',
JSONDecodeError: 'Wrong jwks_host in JWT payload. '
'Make sure domain follows the '
'visibility.<region>.cisco.com structure',

}
expected_errors = (
ConnectionError,
InvalidURL,
JSONDecodeError,
HTTPError,
)
try:
response = requests.get(f"https://{jwks_host}/.well-known/jwks")
response.raise_for_status()
jwks = response.json()

public_keys = {}
Expand All @@ -82,9 +96,8 @@ def get_public_key(jwks_host, token):
kid = jwt.get_unverified_header(token)['kid']
return public_keys.get(kid)

except tuple(expected_errors) as error:
message = expected_errors[error.__class__]
raise AuthorizationError(message)
except expected_errors:
raise AuthorizationError(WRONG_JWKS_HOST)


def get_jwt():
Expand All @@ -93,22 +106,19 @@ def get_jwt():
from /.well-known/jwks endpoint
"""
expected_errors = {
KeyError: 'Wrong JWT payload structure',
AssertionError: 'jwk_host is missing in JWT payload. Make sure '
'custom_jwks_host field is present in module_type',
InvalidSignatureError: 'Failed to decode JWT with provided key. '
'Make sure domain in custom_jwks_host '
'corresponds to your SecureX instance region.',
DecodeError: 'Wrong JWT structure',
InvalidAudienceError: 'Wrong configuration-token-audience',
TypeError: 'kid from JWT header not found in API response'
KeyError: WRONG_PAYLOAD_STRUCTURE,
AssertionError: JWKS_HOST_MISSING,
InvalidSignatureError: WRONG_KEY,
DecodeError: WRONG_JWT_STRUCTURE,
InvalidAudienceError: WRONG_AUDIENCE,
TypeError: KID_NOT_FOUND,
}

token = get_auth_token()
try:
jwks_host = jwt.decode(
token, options={'verify_signature': False}).get('jwks_host')
assert jwks_host
jwks_payload = jwt.decode(token, options={'verify_signature': False})
assert 'jwks_host' in jwks_payload
jwks_host = jwks_payload.get('jwks_host')
key = get_public_key(jwks_host, token)
aud = request.url_root
payload = jwt.decode(
Expand Down
2 changes: 1 addition & 1 deletion code/container_settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"VERSION": "2.0.1",
"VERSION": "2.0.2",
"NAME": "AbuseIPDB"
}
9 changes: 0 additions & 9 deletions code/requirements.txt

This file was deleted.

4 changes: 3 additions & 1 deletion code/tests/unit/api/test_enrich.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,9 @@ def test_enrich_call_wrong_jwks_host_error(route, client, valid_jwt,
def test_enrich_call_jwks_host_missing_error(route, client, valid_jwt,
valid_json):
response = client.post(
route, headers=headers(valid_jwt(jwks_host=None)), json=valid_json
route,
headers=headers(valid_jwt(wrong_jwks_host=True)),
json=valid_json,
)
assert response.status_code == HTTPStatus.OK
assert response.get_json() == EXPECTED_JWKS_HOST_MISSING_ERROR
Expand Down
3 changes: 2 additions & 1 deletion code/tests/unit/api/test_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ def test_health_call_wrong_jwks_host_error(route, client, valid_jwt,

def test_health_call_jwks_host_missing_error(route, client, valid_jwt):

response = client.post(route, headers=headers(valid_jwt(jwks_host=None)))
response = client.post(route,
headers=headers(valid_jwt(wrong_jwks_host=True)))

assert response.status_code == HTTPStatus.OK
assert response.get_json() == EXPECTED_JWKS_HOST_MISSING_ERROR
Expand Down
7 changes: 6 additions & 1 deletion code/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ def _make_jwt(
jwks_host='visibility.amp.cisco.com',
aud='http://localhost',
limit=100,
kid='02B1174234C29F8EFB69911438F597FF3FFEE6B7'
kid='02B1174234C29F8EFB69911438F597FF3FFEE6B7',
wrong_jwks_host=False,
):
payload = {
'key': key,
'jwks_host': jwks_host,
'aud': aud,
'CTR_ENTITIES_LIMIT': limit
}

if wrong_jwks_host:
payload.pop('jwks_host')

return jwt.encode(
payload, client.application.rsa_private_key, algorithm='RS256',
headers={
Expand Down
2 changes: 1 addition & 1 deletion code/tests/unit/mock_for_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@
'errors': [
{
'code': 'authorization error',
'message': 'Authorization failed: jwk_host is missing in JWT '
'message': 'Authorization failed: jwks_host is missing in JWT '
'payload. Make sure custom_jwks_host field is present '
'in module_type',
'type': 'fatal'
Expand Down
6 changes: 0 additions & 6 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
#!/usr/bin/env sh
set -e

# Grab the repository
if [ -n "$GITREPO" ]; then
echo "rm -rf /app && git clone $GITREPO /app"
rm -rf /app && git clone $GITREPO /app
fi


if [ -n "$ALPINEPYTHON" ] ; then
export PYTHONPATH=$PYTHONPATH:/usr/local/lib/$ALPINEPYTHON/site-packages:/usr/lib/$ALPINEPYTHON/site-packages
Expand Down

0 comments on commit 2f3e3b3

Please sign in to comment.