Implement MSC3383: include destination in X-Matrix auth header (#11398)
Co-authored-by: Jan Christian Grünhage <jan.christian@gruenhage.xyz> Co-authored-by: Marcus Hoffmann <bubu@bubu1.eu>pull/12360/merge
parent
fbdee86004
commit
a1f87f57ff
|
@ -0,0 +1 @@
|
||||||
|
Implement [MSC3383](https://github.com/matrix-org/matrix-spec-proposals/pull/3383) for including the destination in server-to-server authentication headers. Contributed by @Bubu and @jcgruenhage for Famedly GmbH.
|
|
@ -124,7 +124,12 @@ def request(
|
||||||
authorization_headers = []
|
authorization_headers = []
|
||||||
|
|
||||||
for key, sig in signed_json["signatures"][origin_name].items():
|
for key, sig in signed_json["signatures"][origin_name].items():
|
||||||
header = 'X-Matrix origin=%s,key="%s",sig="%s"' % (origin_name, key, sig)
|
header = 'X-Matrix origin=%s,key="%s",sig="%s",destination="%s"' % (
|
||||||
|
origin_name,
|
||||||
|
key,
|
||||||
|
sig,
|
||||||
|
destination,
|
||||||
|
)
|
||||||
authorization_headers.append(header.encode("ascii"))
|
authorization_headers.append(header.encode("ascii"))
|
||||||
print("Authorization: %s" % header, file=sys.stderr)
|
print("Authorization: %s" % header, file=sys.stderr)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ import functools
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Tuple, cast
|
from http import HTTPStatus
|
||||||
|
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Tuple, cast
|
||||||
|
|
||||||
from synapse.api.errors import Codes, FederationDeniedError, SynapseError
|
from synapse.api.errors import Codes, FederationDeniedError, SynapseError
|
||||||
from synapse.api.urls import FEDERATION_V1_PREFIX
|
from synapse.api.urls import FEDERATION_V1_PREFIX
|
||||||
|
@ -86,15 +87,24 @@ class Authenticator:
|
||||||
|
|
||||||
if not auth_headers:
|
if not auth_headers:
|
||||||
raise NoAuthenticationError(
|
raise NoAuthenticationError(
|
||||||
401, "Missing Authorization headers", Codes.UNAUTHORIZED
|
HTTPStatus.UNAUTHORIZED,
|
||||||
|
"Missing Authorization headers",
|
||||||
|
Codes.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
for auth in auth_headers:
|
for auth in auth_headers:
|
||||||
if auth.startswith(b"X-Matrix"):
|
if auth.startswith(b"X-Matrix"):
|
||||||
(origin, key, sig) = _parse_auth_header(auth)
|
(origin, key, sig, destination) = _parse_auth_header(auth)
|
||||||
json_request["origin"] = origin
|
json_request["origin"] = origin
|
||||||
json_request["signatures"].setdefault(origin, {})[key] = sig
|
json_request["signatures"].setdefault(origin, {})[key] = sig
|
||||||
|
|
||||||
|
# if the origin_server sent a destination along it needs to match our own server_name
|
||||||
|
if destination is not None and destination != self.server_name:
|
||||||
|
raise AuthenticationError(
|
||||||
|
HTTPStatus.UNAUTHORIZED,
|
||||||
|
"Destination mismatch in auth header",
|
||||||
|
Codes.UNAUTHORIZED,
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
self.federation_domain_whitelist is not None
|
self.federation_domain_whitelist is not None
|
||||||
and origin not in self.federation_domain_whitelist
|
and origin not in self.federation_domain_whitelist
|
||||||
|
@ -103,7 +113,9 @@ class Authenticator:
|
||||||
|
|
||||||
if origin is None or not json_request["signatures"]:
|
if origin is None or not json_request["signatures"]:
|
||||||
raise NoAuthenticationError(
|
raise NoAuthenticationError(
|
||||||
401, "Missing Authorization headers", Codes.UNAUTHORIZED
|
HTTPStatus.UNAUTHORIZED,
|
||||||
|
"Missing Authorization headers",
|
||||||
|
Codes.UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.keyring.verify_json_for_server(
|
await self.keyring.verify_json_for_server(
|
||||||
|
@ -142,13 +154,14 @@ class Authenticator:
|
||||||
logger.exception("Error resetting retry timings on %s", origin)
|
logger.exception("Error resetting retry timings on %s", origin)
|
||||||
|
|
||||||
|
|
||||||
def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
|
def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str, Optional[str]]:
|
||||||
"""Parse an X-Matrix auth header
|
"""Parse an X-Matrix auth header
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
header_bytes: header value
|
header_bytes: header value
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
origin, key id, signature, destination.
|
||||||
origin, key id, signature.
|
origin, key id, signature.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
@ -157,7 +170,9 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
|
||||||
try:
|
try:
|
||||||
header_str = header_bytes.decode("utf-8")
|
header_str = header_bytes.decode("utf-8")
|
||||||
params = header_str.split(" ")[1].split(",")
|
params = header_str.split(" ")[1].split(",")
|
||||||
param_dict = {k: v for k, v in (kv.split("=", maxsplit=1) for kv in params)}
|
param_dict: Dict[str, str] = {
|
||||||
|
k: v for k, v in [param.split("=", maxsplit=1) for param in params]
|
||||||
|
}
|
||||||
|
|
||||||
def strip_quotes(value: str) -> str:
|
def strip_quotes(value: str) -> str:
|
||||||
if value.startswith('"'):
|
if value.startswith('"'):
|
||||||
|
@ -172,7 +187,15 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
|
||||||
|
|
||||||
key = strip_quotes(param_dict["key"])
|
key = strip_quotes(param_dict["key"])
|
||||||
sig = strip_quotes(param_dict["sig"])
|
sig = strip_quotes(param_dict["sig"])
|
||||||
return origin, key, sig
|
|
||||||
|
# get the destination server_name from the auth header if it exists
|
||||||
|
destination = param_dict.get("destination")
|
||||||
|
if destination is not None:
|
||||||
|
destination = strip_quotes(destination)
|
||||||
|
else:
|
||||||
|
destination = None
|
||||||
|
|
||||||
|
return origin, key, sig, destination
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Error parsing auth header '%s': %s",
|
"Error parsing auth header '%s': %s",
|
||||||
|
@ -180,7 +203,7 @@ def _parse_auth_header(header_bytes: bytes) -> Tuple[str, str, str]:
|
||||||
e,
|
e,
|
||||||
)
|
)
|
||||||
raise AuthenticationError(
|
raise AuthenticationError(
|
||||||
400, "Malformed Authorization header", Codes.UNAUTHORIZED
|
HTTPStatus.BAD_REQUEST, "Malformed Authorization header", Codes.UNAUTHORIZED
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -704,6 +704,9 @@ class MatrixFederationHttpClient:
|
||||||
Returns:
|
Returns:
|
||||||
A list of headers to be added as "Authorization:" headers
|
A list of headers to be added as "Authorization:" headers
|
||||||
"""
|
"""
|
||||||
|
if destination is None and destination_is is None:
|
||||||
|
raise ValueError("destination and destination_is cannot both be None!")
|
||||||
|
|
||||||
request: JsonDict = {
|
request: JsonDict = {
|
||||||
"method": method.decode("ascii"),
|
"method": method.decode("ascii"),
|
||||||
"uri": url_bytes.decode("ascii"),
|
"uri": url_bytes.decode("ascii"),
|
||||||
|
@ -726,8 +729,13 @@ class MatrixFederationHttpClient:
|
||||||
for key, sig in request["signatures"][self.server_name].items():
|
for key, sig in request["signatures"][self.server_name].items():
|
||||||
auth_headers.append(
|
auth_headers.append(
|
||||||
(
|
(
|
||||||
'X-Matrix origin=%s,key="%s",sig="%s"'
|
'X-Matrix origin=%s,key="%s",sig="%s",destination="%s"'
|
||||||
% (self.server_name, key, sig)
|
% (
|
||||||
|
self.server_name,
|
||||||
|
key,
|
||||||
|
sig,
|
||||||
|
request.get("destination") or request["destination_is"],
|
||||||
|
)
|
||||||
).encode("ascii")
|
).encode("ascii")
|
||||||
)
|
)
|
||||||
return auth_headers
|
return auth_headers
|
||||||
|
|
Loading…
Reference in New Issue