Co-authored-by: Amaury Chamayou <amchamay@microsoft.com>
Co-authored-by: Amaury Chamayou <amaury@xargs.fr>
This commit is contained in:
Max 2024-08-13 17:23:13 +01:00 коммит произвёл GitHub
Родитель 4089c9193c
Коммит 457511806c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 100 добавлений и 98 удалений

Просмотреть файл

@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
[5.0.3]: https://github.com/microsoft/CCF/releases/tag/ccf-5.0.3
### Changed
- Improved JWT authentication error messages (#6427).
### Bug fix
- In `GET gov/service/javascript-app`, `openApi` now correctly returns the schema set for the endpoint (#6430)

Просмотреть файл

@ -123,10 +123,8 @@ namespace ccf
const auto token_opt =
::http::JwtVerifier::extract_token(headers, error_reason);
if (!token_opt)
{
error_reason = "Invalid JWT token";
return nullptr;
}
@ -153,7 +151,7 @@ namespace ccf
}
}
if (!token_keys)
if (!token_keys || token_keys->empty())
{
error_reason =
fmt::format("JWT signing key not found for kid {}", key_id);
@ -165,6 +163,7 @@ namespace ccf
auto verifier = verifiers->get_verifier(metadata.cert);
if (!::http::JwtVerifier::validate_token_signature(token, verifier))
{
error_reason = "Signature verification failed";
continue;
}
@ -208,7 +207,6 @@ namespace ccf
}
}
error_reason = "Can't authenticate JWT token";
return nullptr;
}

Просмотреть файл

@ -102,17 +102,19 @@ def set_issuer_with_a_key(primary, network, issuer, kid, constraint):
network.consortium.set_jwt_issuer(primary, metadata_fp.name)
def parse_error_message(r):
return r.body.json()["error"]["details"][0]["message"]
def try_auth(primary, issuer, kid, iss, tid):
with primary.client("user0") as c:
LOG.info(f"Creating JWT with kid={kid} iss={iss} tenant={tid}")
r = c.get(
return c.get(
"/app/jwt",
headers=infra.jwt_issuer.make_bearer_header(
issuer.issue_jwt(kid, claims={"iss": iss, "tid": tid})
),
)
assert r.status_code
return r.status_code
@reqs.description("Test stack size limit")
@ -135,7 +137,7 @@ def test_stack_size_limit(network, args):
depth *= 2
r = c.post("/app/recursive", body={"depth": depth})
if r.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR:
message = r.body.json()["error"]["details"][0]["message"]
message = parse_error_message(r)
assert message == "InternalError: stack overflow", message
LOG.info(
f"Stack overflow at depth={depth} with max_stack_bytes={max_stack_bytes}"
@ -184,14 +186,14 @@ def test_heap_size_limit(network, args):
with temporary_js_limits(network, primary, max_heap_bytes=3 * 1024 * 1024):
r = c.post("/app/alloc", body={"size": safe_size})
assert r.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR, r
message = r.body.json()["error"]["details"][0]["message"]
message = parse_error_message(r)
assert message == "InternalError: out of memory", message
r = c.post("/app/alloc", body={"size": safe_size})
assert r.status_code == http.HTTPStatus.OK, r
r = c.post("/app/alloc", body={"size": unsafe_size})
message = r.body.json()["error"]["details"][0]["message"]
message = parse_error_message(r)
assert message == "InternalError: out of memory", message
# Lower the cap until we likely run out of heap out of user code,
@ -231,7 +233,7 @@ def test_execution_time_limit(network, args):
with temporary_js_limits(network, primary, max_execution_time_ms=30):
r = c.post("/app/sleep", body={"time": safe_time})
assert r.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR, r
message = r.body.json()["error"]["details"][0]["message"]
message = parse_error_message(r)
assert message == "InternalError: interrupted", message
r = c.post("/app/sleep", body={"time": safe_time})
@ -239,7 +241,7 @@ def test_execution_time_limit(network, args):
r = c.post("/app/sleep", body={"time": unsafe_time})
assert r.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR, r
message = r.body.json()["error"]["details"][0]["message"]
message = parse_error_message(r)
assert message == "InternalError: interrupted", message
# Lower the cap until we likely run out of heap out of user code,
@ -310,7 +312,7 @@ def test_cert_auth(network, args):
with primary.client(local_user_id) as c:
r = c.get("/app/cert")
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "Not After" in r.body.json()["error"]["details"][0]["message"], r
assert "Not After" in parse_error_message(r), r
LOG.info("User with future cert cannot call user-authenticated endpoint")
local_user_id = "in_the_future"
@ -322,7 +324,7 @@ def test_cert_auth(network, args):
with primary.client(local_user_id) as c:
r = c.get("/app/cert")
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "Not Before" in r.body.json()["error"]["details"][0]["message"], r
assert "Not Before" in parse_error_message(r), r
LOG.info("No leeway added to cert time evaluation")
local_user_id = "just_expired"
@ -333,7 +335,7 @@ def test_cert_auth(network, args):
with primary.client(local_user_id) as c:
r = c.get("/app/cert")
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "Not After" in r.body.json()["error"]["details"][0]["message"], r
assert "Not After" in parse_error_message(r), r
return network
@ -354,11 +356,13 @@ def test_jwt_auth(network, args):
with primary.client("user0") as c:
r = c.get("/app/jwt", headers=infra.jwt_issuer.make_bearer_header("garbage"))
assert r.status_code == HTTPStatus.UNAUTHORIZED, r.status_code
assert "Malformed JWT" in parse_error_message(r), r
jwt_mismatching_key_priv_pem, _ = infra.crypto.generate_rsa_keypair(2048)
jwt = infra.crypto.create_jwt({}, jwt_mismatching_key_priv_pem, jwt_kid)
r = c.get("/app/jwt", headers=infra.jwt_issuer.make_bearer_header(jwt))
assert r.status_code == HTTPStatus.UNAUTHORIZED, r.status_code
assert "JWT payload is missing required field" in parse_error_message(r), r
r = c.get(
"/app/jwt",
@ -374,6 +378,7 @@ def test_jwt_auth(network, args):
),
)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r.status_code
assert "is before token's Not Before" in parse_error_message(r), r
LOG.info("Calling JWT with too-early exp")
r = c.get(
@ -383,6 +388,7 @@ def test_jwt_auth(network, args):
),
)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r.status_code
assert "is after token's Expiration Time" in parse_error_message(r), r
network.consortium.remove_jwt_issuer(primary, issuer.name)
return network
@ -404,22 +410,22 @@ def test_jwt_auth_msft_single_tenant(network, args):
set_issuer_with_a_key(primary, network, issuer, jwt_kid, ISSUER_TENANT)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, "garbage_tenant")
== HTTPStatus.UNAUTHORIZED
)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, "{tenantid}")
== HTTPStatus.UNAUTHORIZED
)
assert try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, "garbage_tenant")
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, "{tenantid}")
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
network.consortium.remove_jwt_issuer(primary, issuer.name)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid}" in parse_error_message(r), r
return network
@ -469,36 +475,32 @@ def test_jwt_auth_msft_multitenancy(network, args):
metadata_fp.flush()
network.consortium.set_jwt_issuer(primary, metadata_fp.name)
assert (
try_auth(primary, issuer, jwt_kid_1, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
)
assert (
try_auth(primary, issuer, jwt_kid_1, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.OK
)
assert (
try_auth(primary, issuer, jwt_kid_1, ISSUER_TENANT, ANOTHER_TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid_1, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
assert (
try_auth(primary, issuer, jwt_kid_2, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
)
assert (
try_auth(primary, issuer, jwt_kid_2, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid_1, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
r = try_auth(primary, issuer, jwt_kid_1, ISSUER_TENANT, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
r = try_auth(primary, issuer, jwt_kid_2, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
r = try_auth(primary, issuer, jwt_kid_2, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
network.consortium.remove_jwt_issuer(primary, issuer.name)
assert (
try_auth(primary, issuer, jwt_kid_1, ISSUER_TENANT, TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
assert (
try_auth(primary, issuer, jwt_kid_2, ISSUER_TENANT, TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid_1, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid_1}" in parse_error_message(r), r
r = try_auth(primary, issuer, jwt_kid_2, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid_2}" in parse_error_message(r), r
return network
@ -528,41 +530,39 @@ def test_jwt_auth_msft_same_kids_different_issuers(network, args):
set_issuer_with_a_key(primary, network, issuer, jwt_kid, ISSUER_TENANT)
assert try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
assert (
try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
r = try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
set_issuer_with_a_key(primary, network, another, jwt_kid, ISSUER_ANOTHER)
assert try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
assert (
try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.OK
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
r = try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
network.consortium.remove_jwt_issuer(primary, issuer.name)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
assert (
try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.OK
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
r = try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
network.consortium.remove_jwt_issuer(primary, another.name)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
assert (
try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid}" in parse_error_message(r), r
r = try_auth(primary, another, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid}" in parse_error_message(r), r
return network
@ -587,30 +587,30 @@ def test_jwt_auth_msft_same_kids_overwrite_constraint(network, args):
set_issuer_with_a_key(primary, network, issuer, jwt_kid, COMMNON_ISSUER)
assert try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.OK
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
r = try_auth(primary, issuer, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
set_issuer_with_a_key(primary, network, issuer, jwt_kid, ISSUER_TENANT)
assert try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID) == HTTPStatus.OK
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.OK, r
r = try_auth(primary, issuer, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert "failed issuer constraint validation" in parse_error_message(r), r
network.consortium.remove_jwt_issuer(primary, issuer.name)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
assert (
try_auth(primary, issuer, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
== HTTPStatus.UNAUTHORIZED
)
r = try_auth(primary, issuer, jwt_kid, ISSUER_TENANT, TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid}" in parse_error_message(r), r
r = try_auth(primary, issuer, jwt_kid, ISSUER_ANOTHER, ANOTHER_TENANT_ID)
assert r.status_code == HTTPStatus.UNAUTHORIZED, r
assert f"key not found for kid {jwt_kid}" in parse_error_message(r), r
return network