Refactor exceptions

These exception are now used
- `CertAPISystemError` should be used for sentinel-internal errors
  - its child `InvalidRedisDataError` is risen when validation of data
    saved in Redis failes
- `RequestConsistencyError` should be used for client request that cant be
   processed
- `RequestProcessError` should be used for client request that can be processed,
   but the provided data is invalid, including checker/CA authentication
   failure

Among these exception the logging should be done this way:
- `RequestConsistencyError` and `RequestProcessError` should be logged on it's
  first occurence with levels `debug`, `info` and `warning` for most severe
  cases
- `CertAPISystemError` should be logged centrally in one place with levels
  `error` for most cases and `critical` when the application needs to stop
- `InvalidRedisDataError` is never logged
parent 12beb71a
"""
Note on exceptions:
- CertAPISystemError should be used for sentinel-internal errors
- RequestConsistencyError should be used for client request that cant be
processed
- RequestProcessError should be used for client request that can be processed,
but the provided data is invalid, including checker/CA authentication
failure
Note on logging:
- ClientDataError and ClientAuthError should be logged on it's first
occurence with levels
'debug', 'info' and 'warning' for most severe cases
- RequestConsistencyError and RequestProcessError should be logged on it's
first occurence with levels 'debug', 'info' and 'warning' for most severe
cases
- CertAPISystemError should be logged centrally in one place with levels
'error' for most cases and 'critical' when the application needs to stop
"""
......@@ -11,9 +18,8 @@ import json
from flask import current_app
from .crypto import create_random_sid, create_random_nonce, key_match
from .exceptions import ClientDataError, ClientAuthError, CertAPISystemError, \
InvalidSessionError, InvalidAuthStateError, \
AuthStateMissing
from .exceptions import RequestConsistencyError, RequestProcessError, CertAPISystemError, \
InvalidRedisDataError
from .validators import check_request, validate_auth_state, check_session
DELAY_GET_SESSION_EXISTS = 10
......@@ -21,6 +27,10 @@ DELAY_AUTH = 10
DELAY_AUTH_AGAIN = 10
class AuthStateMissing(Exception):
pass
def build_reply_auth_accepted(delay=DELAY_AUTH):
return {
"status": "accepted",
......@@ -99,7 +109,7 @@ def check_auth_state(sn, sid, r):
try:
auth_state = json.loads(auth_state.decode("utf-8"))
validate_auth_state(auth_state)
except (UnicodeDecodeError, json.decoder.JSONDecodeError, InvalidAuthStateError) as e:
except (UnicodeDecodeError, json.decoder.JSONDecodeError, InvalidRedisDataError) as e:
raise CertAPISystemError("{} for auth_state sn={}, sid={}".format(e, sn, sid))
if auth_state["status"] == "error":
......@@ -108,7 +118,7 @@ def check_auth_state(sn, sid, r):
if auth_state["status"] == "fail":
current_app.logger.debug("fail status for auth_state sn=%s, sid=%s,"
" (message=%s)", sn, sid, auth_state["message"])
raise ClientAuthError(auth_state["message"])
raise RequestProcessError(auth_state["message"])
def process_req_get(sn, sid, csr_str, flags, auth_type, r):
......@@ -154,12 +164,12 @@ def get_auth_session(sn, sid, r):
session_json = r.get(get_session_key(sn, sid))
if not session_json: # authentication session open / certificate creation in progress
current_app.logger.debug("Authentication session not found, sn=%s, sid=%s", sn, sid)
raise ClientAuthError("Auth session not found. Did you send 'get_cert' request?")
raise RequestProcessError("Auth session not found. Did you send 'get_cert' request?")
try:
session = json.loads(session_json.decode("utf-8"))
check_session(session)
except (UnicodeDecodeError, json.decoder.JSONDecodeError, InvalidSessionError) as e:
except (UnicodeDecodeError, json.decoder.JSONDecodeError, InvalidRedisDataError) as e:
raise CertAPISystemError("{} for sn={}, sid={}".format(e, sn, sid))
return session
......@@ -195,11 +205,11 @@ def process_req_auth(sn, sid, digest, auth_type, r):
current_app.logger.debug("Authentication session found open for sn=%s, sid=%s", sn, sid)
if session["auth_type"] != auth_type:
current_app.logger.debug("Authentication type does not match, sn=%s, sid=%s", sn, sid)
raise ClientAuthError("Auth type does not match the original one")
raise RequestProcessError("Auth type does not match the original one")
if session["digest"]: # certificate creation in progress
current_app.logger.debug("Digest already saved for sn=%s, sid=%s", sn, sid)
raise ClientAuthError("Digest already saved")
raise RequestProcessError("Digest already saved")
# start certificate creation (CA will check the digest first)
current_app.logger.debug("Saving digest for sn=%s, sid=%s", sn, sid)
......@@ -224,9 +234,9 @@ def process_request(req, r):
req["digest"],
req["auth_type"],
r)
except ClientAuthError as e:
except RequestProcessError as e:
return build_reply("fail", str(e))
except ClientDataError as e:
except RequestConsistencyError as e:
return build_reply("error", str(e))
except CertAPISystemError as e:
current_app.logger.error(str(e))
......
......@@ -4,7 +4,7 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography import x509
from .exceptions import InvalidParamError
from .exceptions import RequestConsistencyError
AVAIL_HASHES = {
"sha224",
......@@ -27,7 +27,7 @@ def csr_from_str(csr_str):
backend=default_backend()
)
except (UnicodeEncodeError, ValueError):
raise InvalidParamError("Invalid CSR format")
raise RequestConsistencyError("Invalid CSR format")
return csr
......
......@@ -2,19 +2,11 @@ class CertAPIError(Exception):
pass
class CertAPIClientError(CertAPIError):
class RequestConsistencyError(CertAPIError):
pass
class ClientDataError(CertAPIClientError):
pass
class ClientAuthError(CertAPIClientError):
pass
class InvalidParamError(ClientDataError):
class RequestProcessError(CertAPIError):
pass
......@@ -22,13 +14,5 @@ class CertAPISystemError(CertAPIError):
pass
class InvalidSessionError(CertAPISystemError):
pass
class InvalidAuthStateError(CertAPISystemError):
pass
class AuthStateMissing(Exception):
class InvalidRedisDataError(CertAPISystemError):
pass
from .crypto import AVAIL_HASHES, get_common_names, csr_from_str
from .exceptions import InvalidParamError, InvalidAuthStateError, InvalidSessionError, ClientDataError
from .exceptions import RequestConsistencyError, InvalidRedisDataError
AVAIL_REQUEST_TYPES = {"get_cert", "auth"}
AVAIL_FLAGS = {"renew"}
......@@ -33,15 +33,15 @@ AUTH_REQ_PARAMS = {
def validate_sn_atsha(sn):
if len(sn) != 16:
raise InvalidParamError("SN has invalid length.")
raise RequestConsistencyError("SN has invalid length.")
if sn[0:5] != "00000":
raise InvalidParamError("SN has invalid format.")
raise RequestConsistencyError("SN has invalid format.")
try:
sn_value = int(sn, 16)
except ValueError:
raise InvalidParamError("SN has invalid format.")
raise RequestConsistencyError("SN has invalid format.")
if sn_value % 11 != 0:
raise InvalidParamError("SN has invalid format.")
raise RequestConsistencyError("SN has invalid format.")
sn_validators = {
......@@ -52,22 +52,22 @@ sn_validators = {
def validate_csr_common_name(csr, identity):
common_names = get_common_names(csr)
if len(common_names) != 1:
raise InvalidParamError("CSR has not exactly one CommonName")
raise RequestConsistencyError("CSR has not exactly one CommonName")
common_name = common_names[0].value
if common_name != identity:
raise InvalidParamError("CSR CommonName ({}) does not match desired identity".format(common_name))
raise RequestConsistencyError("CSR CommonName ({}) does not match desired identity".format(common_name))
def validate_csr_hash(csr):
h = csr.signature_hash_algorithm.name
if h not in AVAIL_HASHES:
raise InvalidParamError("CSR is signed with not allowed hash ({})".format(h))
raise RequestConsistencyError("CSR is signed with not allowed hash ({})".format(h))
def validate_csr_signature(csr):
if not csr.is_signature_valid:
raise InvalidParamError("Request signature is not valid")
raise RequestConsistencyError("Request signature is not valid")
def validate_csr(csr, sn):
......@@ -80,66 +80,66 @@ def validate_csr(csr, sn):
def validate_flags(flags):
for flag in flags:
if flag not in AVAIL_FLAGS:
raise InvalidParamError("Flag not available: {}".format(flag))
raise RequestConsistencyError("Flag not available: {}".format(flag))
def validate_req_type(req_type):
if req_type not in AVAIL_REQUEST_TYPES:
raise InvalidParamError("Invalid request type: {}".format(req_type))
raise RequestConsistencyError("Invalid request type: {}".format(req_type))
def validate_sid(sid):
if sid == "":
return
if (len(sid) != 64 or not sid.islower()):
raise InvalidParamError("Bad format of sid: {}".format(sid))
raise RequestConsistencyError("Bad format of sid: {}".format(sid))
try:
sid = int(sid, 16)
except ValueError:
raise InvalidParamError("Bad format of sid: {}".format(sid))
raise RequestConsistencyError("Bad format of sid: {}".format(sid))
def validate_digest(digest):
if len(digest) != 64:
raise InvalidParamError("Bad format of digest: {}".format(digest))
raise RequestConsistencyError("Bad format of digest: {}".format(digest))
try:
digest = int(digest, 16)
except ValueError:
raise InvalidParamError("Bad format of digest: {}".format(digest))
raise RequestConsistencyError("Bad format of digest: {}".format(digest))
def validate_auth_type(auth_type):
if auth_type not in sn_validators:
raise InvalidParamError("Invalid auth type: {}".format(auth_type))
raise RequestConsistencyError("Invalid auth type: {}".format(auth_type))
def check_session(session):
if type(session) is not dict:
raise InvalidSessionError("Must be a dict!")
raise InvalidRedisDataError("Must be a dict!")
for param in SESSION_PARAMS:
if param not in session:
raise InvalidSessionError("{} missing in session".format(param))
raise InvalidRedisDataError("Parameter {} missing".format(param))
def validate_auth_state(auth_state):
if type(auth_state) is not dict:
raise InvalidAuthStateError("Must be a dict!")
raise InvalidRedisDataError("Must be a dict!")
for param in AUTH_STATE_PARAMS:
if param not in auth_state:
raise InvalidAuthStateError("Parameter {} missing".format(param))
raise InvalidRedisDataError("Parameter {} missing".format(param))
if auth_state["status"] not in AVAIL_STATES:
raise InvalidAuthStateError("Invalid status '{}'".format(auth_state["status"]))
raise InvalidRedisDataError("Invalid status '{}'".format(auth_state["status"]))
def check_params_exist(req, params):
for param in GENERAL_REQ_PARAMS:
if param not in req:
raise ClientDataError("'{}' is missing in the request".format(param))
raise RequestConsistencyError("'{}' is missing in the request".format(param))
def check_request(req):
if type(req) is not dict:
raise ClientDataError("Request not a valid JSON with correct content type")
raise RequestConsistencyError("Request not a valid JSON with correct content type")
check_params_exist(req, GENERAL_REQ_PARAMS)
validate_req_type(req["type"])
validate_auth_type(req["auth_type"])
......@@ -149,16 +149,16 @@ def check_request(req):
if req["type"] == "get_cert":
if len(req) > (len(GENERAL_REQ_PARAMS) + len(GET_CERT_REQ_PARAMS)):
raise ClientDataError("Too much parameters in request")
raise RequestConsistencyError("Too much parameters in request")
check_params_exist(req, AUTH_REQ_PARAMS)
validate_csr(req["csr"], req["sn"])
validate_flags(req["flags"])
if "renew" in req["flags"] and req["sid"]:
raise InvalidParamError("Renew allowed only in the first request")
raise RequestConsistencyError("Renew allowed only in the first request")
elif req["type"] == "auth":
if len(req) > (len(GENERAL_REQ_PARAMS) + len(AUTH_REQ_PARAMS)):
raise ClientDataError("Too much parameters in request")
raise RequestConsistencyError("Too much parameters in request")
check_params_exist(req, GET_CERT_REQ_PARAMS)
validate_digest(req["digest"])
......@@ -17,7 +17,7 @@ def test_valid_csr_loading(good_pem_csr, good_public_numbers):
def test_invalid_csr_loading(bad_pem_csr, good_public_numbers):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
c.csr_from_str(bad_pem_csr)
......
......@@ -10,7 +10,7 @@ def test_valid_sn_atsha(good_sn_atsha):
def test_invalid_sn_atsha(bad_sn_atsha):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
v.validate_sn_atsha(bad_sn_atsha)
......@@ -34,7 +34,7 @@ def test_valid_csr(good_csr, good_sn_atsha):
def test_invalid_csr(bad_csr, bad_sn_atsha):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
v.validate_csr(bad_csr, bad_sn_atsha)
......@@ -43,7 +43,7 @@ def test_valid_req_type(good_req_types):
def test_invalid_req_type(bad_req_types):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
v.validate_req_type(bad_req_types)
......@@ -52,7 +52,7 @@ def test_valid_flags(good_flags):
def test_invalid_flags(bad_flags):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
v.validate_flags(bad_flags)
......@@ -61,7 +61,7 @@ def test_valid_auth_type(good_auth_types):
def test_invalid_auth_type(bad_auth_types):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
v.validate_auth_type(bad_auth_types)
......@@ -70,7 +70,7 @@ def test_valid_sn(good_sid):
def test_invalid_sn(bad_sid):
with pytest.raises(ex.InvalidParamError):
with pytest.raises(ex.RequestConsistencyError):
v.validate_sid(bad_sid)
......@@ -79,7 +79,7 @@ def test_valid_session(good_sessions):
def test_invalid_session(bad_sessions):
with pytest.raises(ex.InvalidSessionError):
with pytest.raises(ex.InvalidRedisDataError):
v.check_session(bad_sessions)
......@@ -88,5 +88,5 @@ def test_valid_auth_state(good_auth_state):
def test_invalid_auth_state(bad_auth_state):
with pytest.raises(ex.InvalidAuthStateError):
with pytest.raises(ex.InvalidRedisDataError):
v.validate_auth_state(bad_auth_state)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment