Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions contrib/runners/http_runner/http_runner/http_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from __future__ import absolute_import

import ast
import copy
import json
Expand All @@ -21,6 +22,7 @@
import requests
from requests.auth import HTTPBasicAuth
from oslo_config import cfg
from six.moves.urllib import parse as urlparse # pylint: disable=import-error

from st2common.runners.base import ActionRunner
from st2common.runners.base import get_metadata as get_runner_metadata
Expand Down Expand Up @@ -55,6 +57,7 @@
RUNNER_VERIFY_SSL_CERT = 'verify_ssl_cert'
RUNNER_USERNAME = 'username'
RUNNER_PASSWORD = 'password'
RUNNER_HOSTS_BLACKLIST = 'hosts_blacklist'

# Lookup constants for action params
ACTION_AUTH = 'auth'
Expand Down Expand Up @@ -93,6 +96,7 @@ def pre_run(self):
self._http_proxy = self.runner_parameters.get(RUNNER_HTTP_PROXY, None)
self._https_proxy = self.runner_parameters.get(RUNNER_HTTPS_PROXY, None)
self._verify_ssl_cert = self.runner_parameters.get(RUNNER_VERIFY_SSL_CERT, None)
self._hosts_blacklist = self.runner_parameters.get(RUNNER_HOSTS_BLACKLIST, [])

def run(self, action_parameters):
client = self._get_http_client(action_parameters)
Expand Down Expand Up @@ -147,7 +151,8 @@ def _get_http_client(self, action_parameters):
headers=headers, cookies=self._cookies, auth=auth,
timeout=timeout, allow_redirects=self._allow_redirects,
proxies=proxies, files=files, verify=self._verify_ssl_cert,
username=self._username, password=self._password)
username=self._username, password=self._password,
hosts_blacklist=self._hosts_blacklist)

@staticmethod
def _get_result_status(status_code):
Expand All @@ -158,7 +163,8 @@ def _get_result_status(status_code):
class HTTPClient(object):
def __init__(self, url=None, method=None, body='', params=None, headers=None, cookies=None,
auth=None, timeout=60, allow_redirects=False, proxies=None,
files=None, verify=False, username=None, password=None):
files=None, verify=False, username=None, password=None,
hosts_blacklist=None):
if url is None:
raise Exception('URL must be specified.')

Expand Down Expand Up @@ -188,12 +194,19 @@ def __init__(self, url=None, method=None, body='', params=None, headers=None, co
self.verify = verify
self.username = username
self.password = password
self.hosts_blacklist = hosts_blacklist or []

def run(self):
results = {}
resp = None
json_content = self._is_json_content()

# Check if the provided URL is blacklisted
is_url_blacklisted = self._is_url_blacklisted(url=self.url)

if is_url_blacklisted:
raise ValueError('URL "%s" is blacklisted' % (self.url))

try:
if json_content:
# cast params (body) to dict
Expand Down Expand Up @@ -301,6 +314,27 @@ def _cast_object(self, value):
else:
return value

def _is_url_blacklisted(self, url):
"""
Verify if the provided URL is blacklisted via hosts_blacklist runner parameter.
"""
if not self.hosts_blacklist:
# Blacklist is empty
return False

parsed = urlparse.urlparse(url)

# Remove the port and []
host = parsed.netloc.replace('[', '').replace(']', '')

if parsed.port is not None:
host = host.replace(':%s' % (parsed.port), '')

if host in self.hosts_blacklist:
return True

return False


def get_runner():
return HttpRunner(str(uuid.uuid4()))
Expand Down
7 changes: 7 additions & 0 deletions contrib/runners/http_runner/http_runner/runner.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
CA bundle which comes from Mozilla. Verification using a custom CA bundle
is not yet supported. Set to False to skip verification.
type: boolean
hosts_blacklist:
description: Optional list of hosts (network locations) to blacklist (e.g. example.com,
127.0.0.1, etc.)
required: false
type: array
items:
type: string
output_key: body
output_schema:
status_code:
Expand Down
57 changes: 57 additions & 0 deletions contrib/runners/http_runner/tests/unit/test_http_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

from __future__ import absolute_import

import re

import six
import mock
import unittest2
Expand Down Expand Up @@ -212,3 +214,58 @@ def test_http_unicode_body_data(self, mock_requests):
expected_data = body

self.assertEqual(call_kwargs['data'], expected_data)

@mock.patch('http_runner.http_runner.requests')
def test_blacklisted_url_netloc(self, mock_requests):
# Black list is empty
self.assertEqual(mock_requests.request.call_count, 0)

url = 'http://www.example.com'
client = HTTPClient(url=url, method='GET')
client.run()

self.assertEqual(mock_requests.request.call_count, 1)

# Blacklist is set
hosts_blacklist = [
'example.com',
'127.0.0.1',
'::1',
'2001:0db8:85a3:0000:0000:8a2e:0370:7334'
]

# Blacklisted urls
urls = [
'https://example.com',
'http://example.com',
'http://example.com:81',
'http://example.com:80',
'http://example.com:9000',
'http://[::1]:80/',
'http://[::1]',
'http://[::1]:9000',
'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]',
'https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8000'
]

for url in urls:
expected_msg = r'URL "%s" is blacklisted' % (re.escape(url))
client = HTTPClient(url=url, method='GET', hosts_blacklist=hosts_blacklist)
self.assertRaisesRegexp(ValueError, expected_msg, client.run)

# Non blacklisted URLs
urls = [
'https://example2.com',
'http://example3.com',
'http://example4.com:81'
]

for url in urls:
mock_requests.request.reset_mock()

self.assertEqual(mock_requests.request.call_count, 0)

client = HTTPClient(url=url, method='GET')
client.run()

self.assertEqual(mock_requests.request.call_count, 1)
Copy link
Contributor

@m4dcoder m4dcoder Aug 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a unit test where both whitelist and blacklist are provided?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in c07f6d6.

While at it, I might also look at adding some integration tests (if it doesn't become too big of a rabbit hole), since we don't have any at the moment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the time being, I just decided to add some additional unit tests for the runner class itself - 25b43cb.