util.secscan.api: init read-only failover
This commit is contained in:
parent
b4efa7e45b
commit
e81926fcba
2 changed files with 53 additions and 22 deletions
|
@ -310,6 +310,9 @@ class DefaultConfig(object):
|
||||||
# If specified, the endpoint to be used for all POST calls to the security scanner.
|
# If specified, the endpoint to be used for all POST calls to the security scanner.
|
||||||
SECURITY_SCANNER_ENDPOINT_BATCH = None
|
SECURITY_SCANNER_ENDPOINT_BATCH = None
|
||||||
|
|
||||||
|
# If specified, GET requests that return non-200 will be retried at the following instances.
|
||||||
|
SECURITY_SCANNER_READONLY_FAILOVER_ENDPOINTS = []
|
||||||
|
|
||||||
# The indexing engine version running inside the security scanner.
|
# The indexing engine version running inside the security scanner.
|
||||||
SECURITY_SCANNER_ENGINE_VERSION_TARGET = 2
|
SECURITY_SCANNER_ENGINE_VERSION_TARGET = 2
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from flask import url_for
|
||||||
from data.database import CloseForLongOperation
|
from data.database import CloseForLongOperation
|
||||||
from data import model
|
from data import model
|
||||||
from data.model.storage import get_storage_locations
|
from data.model.storage import get_storage_locations
|
||||||
|
from util.failover import failover, FailoverException
|
||||||
from util.secscan.validator import SecurityConfigValidator
|
from util.secscan.validator import SecurityConfigValidator
|
||||||
from util.security.instancekeys import InstanceKeys
|
from util.security.instancekeys import InstanceKeys
|
||||||
from util.security.registry_jwt import generate_bearer_token, build_context_and_subject
|
from util.security.registry_jwt import generate_bearer_token, build_context_and_subject
|
||||||
|
@ -19,6 +20,9 @@ TOKEN_VALIDITY_LIFETIME_S = 60 # Amount of time the security scanner has to cal
|
||||||
|
|
||||||
UNKNOWN_PARENT_LAYER_ERROR_MSG = 'worker: parent layer is unknown, it must be processed first'
|
UNKNOWN_PARENT_LAYER_ERROR_MSG = 'worker: parent layer is unknown, it must be processed first'
|
||||||
|
|
||||||
|
MITM_CERT_PATH = '/conf/mitm.cert'
|
||||||
|
DEFAULT_HTTP_HEADERS = {'Connection': 'close'}
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -309,32 +313,56 @@ class SecurityScannerAPI(object):
|
||||||
|
|
||||||
return json_response
|
return json_response
|
||||||
|
|
||||||
def _call(self, method, relative_url, params=None, body=None):
|
def _request(self, method, endpoint, path, body, params, timeout):
|
||||||
""" Issues an HTTP call to the sec API at the given relative URL.
|
""" Issues an HTTP request to the security endpoint. """
|
||||||
This function disconnects from the database while awaiting a response
|
if self._config is None:
|
||||||
from the API server.
|
raise Exception('Cannot call unconfigured security system')
|
||||||
|
|
||||||
|
url = _join_api_url(endpoint, self._config.get('SECURITY_SCANNER_API_VERSION', 'v1'), path)
|
||||||
|
signer_proxy_url = self._config.get('JWTPROXY_SIGNER', 'localhost:8080')
|
||||||
|
|
||||||
|
logger.debug('%sing security URL %s', method.upper(), url)
|
||||||
|
return self._client.request(method, url, json=body, params=params, timeout=timeout,
|
||||||
|
verify=MITM_CERT_PATH, headers=DEFAULT_HTTP_HEADERS,
|
||||||
|
proxies={'https': 'https://' + signer_proxy_url,
|
||||||
|
'http': 'http://' + signer_proxy_url})
|
||||||
|
|
||||||
|
def _call(self, method, path, params=None, body=None):
|
||||||
|
""" Issues an HTTP request to the security endpoint handling the logic of using an alternative
|
||||||
|
BATCH endpoint for non-GET requests and failover for GET requests.
|
||||||
"""
|
"""
|
||||||
if self._config is None:
|
if self._config is None:
|
||||||
raise Exception('Cannot call unconfigured security system')
|
raise Exception('Cannot call unconfigured security system')
|
||||||
|
|
||||||
client = self._client
|
timeout = self._config['SECURITY_SCANNER_API_TIMEOUT_SECONDS']
|
||||||
headers = {'Connection': 'close'}
|
|
||||||
|
|
||||||
timeout = self._config.get('SECURITY_SCANNER_API_TIMEOUT_SECONDS', 10)
|
|
||||||
endpoint = self._config['SECURITY_SCANNER_ENDPOINT']
|
endpoint = self._config['SECURITY_SCANNER_ENDPOINT']
|
||||||
if method != 'GET':
|
|
||||||
timeout = self._config.get('SECURITY_SCANNER_API_BATCH_TIMEOUT_SECONDS', timeout)
|
|
||||||
endpoint = self._config.get('SECURITY_SCANNER_ENDPOINT_BATCH') or endpoint
|
|
||||||
|
|
||||||
api_url = urljoin(endpoint, '/' + self._config.get('SECURITY_SCANNER_API_VERSION', 'v1')) + '/'
|
|
||||||
url = urljoin(api_url, relative_url)
|
|
||||||
signer_proxy_url = self._config.get('JWTPROXY_SIGNER', 'localhost:8080')
|
|
||||||
|
|
||||||
with CloseForLongOperation(self._config):
|
with CloseForLongOperation(self._config):
|
||||||
logger.debug('%sing security URL %s', method.upper(), url)
|
# If the request isn't a read, attempt to use a batch stack and do not fail over.
|
||||||
return client.request(method, url, json=body, params=params, timeout=timeout,
|
if method != 'GET':
|
||||||
verify='/conf/mitm.cert', headers=headers,
|
if self._config.get('SECURITY_SCANNER_ENDPOINT_BATCH') is not None:
|
||||||
proxies={
|
endpoint = self._config['SECURITY_SCANNER_ENDPOINT_BATCH']
|
||||||
'https': 'https://' + signer_proxy_url,
|
timeout = self._config.get('SECURITY_SCANNER_API_BATCH_TIMEOUT_SECONDS') or timeout
|
||||||
'http': 'http://' + signer_proxy_url
|
return self._request(method, endpoint, path, body, params, timeout)
|
||||||
})
|
|
||||||
|
# The request is read-only and can failover.
|
||||||
|
all_endpoints = [endpoint] + self._config['SECURITY_SCANNER_READONLY_FAILOVER_ENDPOINTS']
|
||||||
|
try:
|
||||||
|
return _failover_read_request(*[((self._request, endpoint, path, body, params, timeout), {})
|
||||||
|
for endpoint in all_endpoints])
|
||||||
|
except FailoverException:
|
||||||
|
raise APIRequestFailure()
|
||||||
|
|
||||||
|
|
||||||
|
def _join_api_url(endpoint, api_version, path):
|
||||||
|
pathless_url = urljoin(endpoint, '/' + api_version) + '/'
|
||||||
|
return urljoin(pathless_url, path)
|
||||||
|
|
||||||
|
|
||||||
|
@failover
|
||||||
|
def _failover_read_request(request_fn, endpoint, path, body, params, timeout):
|
||||||
|
""" This function auto-retries read-only requests until they return a 2xx status code. """
|
||||||
|
resp = request_fn('GET', endpoint, path, body, params, timeout)
|
||||||
|
if resp.status_code / 100 != 2:
|
||||||
|
raise FailoverException('status code was not 2xx')
|
||||||
|
return resp
|
||||||
|
|
Reference in a new issue