SealSkin

proxy_auth

The sign-in a reverse proxy vouches for, and the check that the proxy can be believed.

A proxy that authenticates users in front of this node (Authelia, Authentik, oauth2-proxy, or another in forward-auth mode) names the user in a request header, proxy_auth_user_header, and may list the user's groups in proxy_auth_groups_header. believed says whether the headers of a request count:

  • The connection comes from an address in trusted_proxies.
  • The address is not a session's. Sessions sit on the node's own network, so a network listed whole in trusted_proxies would otherwise let a session's desktop name any user.
  • The proxy replaces what a visitor sends under those names. One that passes a header on lets anyone write it: with the user header that is a proxy whose authentication was left out of this site, and with the groups header one that sets the user and leaves the groups to the visitor.

The last is found out two ways, both by a request for CHECK_PATH whose sign-in headers hold FORGED, which answer serves and reports on:

  • check sends one with no credentials to the node's public entrance. A proxy that authenticates answers for itself, or passes the request on without the headers.
  • check sends another with the cookies of the request whose headers are in question, which are the proxy's own sign-in. The proxy passes that one on, with the headers it manages replaced, so a header it leaves to the visitor shows before the request that brought the cookies is believed.
  • The web app sends one from the browser of a user the proxy signed in, for a proxy whose sign-in the node cannot repeat. answer notes a header that arrives as the page wrote it.

check ends in one of three states. guarded: nothing forged arrived. open: a forged header arrived, to the check or from a browser within FORGED_SECONDS, and header sign-ins are refused. unreachable: the node could not reach its own entrance, by public_url or at a trusted proxy's address, so nothing is known; header sign-ins are refused then too unless proxy_auth_unchecked says to believe the proxy regardless.

A guarded result is kept for GUARDED_SECONDS and then renewed in the background by the next request; any other is asked again after RETRY_SECONDS, so a proxy that was put right is believed at once.

attributelogger
= logging.getLogger(__name__)
attributeCHECK_PATH
= '/api/auth/proxy'
attributeMARK
= 'proxy-check'
attributeFORGED
= 'sealskin forged'
attributeGUARDED_SECONDS
= 60
attributeRETRY_SECONDS
= 15
attributeTIMEOUT_SECONDS
= 3
attributeFORGED_SECONDS
= 300
attributeFRESH_SECONDS
= 10
funcenabled() -> bool

Whether a proxy's header signs users in at all.

Returns

bool
funcheaders() -> list[str]

Return the names of the headers a proxy signs users in with.

Returns

list[str]
funcanswer(request) -> dict[str, Any]

Return what a request for CHECK_PATH carried when it arrived here, noting forged headers.

paramrequestRequest

Returns

dict

headers names the sign-in headers, user and groups are their

func_recent(within=FORGED_SECONDS) -> list[str]

Return the headers that arrived forged from a trusted proxy in the last within seconds.

paramwithinfloat
= FORGED_SECONDS

Returns

list[str]
func_settings_key() -> tuple[str, ...]

Return the settings a result holds for.

Returns

tuple[str, ...]
func_targets() -> list[tuple[str, dict[str, str], dict[str, Any], bool]]

Return the ways to reach this node's entrance, as (url, headers, extensions, verify).

First the public URL as a browser resolves it, believed only under a certificate for that name: whatever else answers at the address, as a router's own page does from inside its network, is not the proxy. Where the node cannot reach the public URL, each single address in trusted_proxies is asked under the public name, on the public port and on 443; the administrator named that address, so its certificate is not checked.

Returns

list[tuple[str, dict[str, str], dict[str, typing.Any], bool]]
func_read(response) -> tuple[str, str]

Turn the entrance's answer into a state and the sentence that explains it.

paramresponsehttpx.Response

Returns

tuple[str, str]
func_passes_on(forged) -> str

Say which headers the proxy passes on as a visitor wrote them.

paramforgedlist[str]

Returns

str
func_probe(cookies='') -> tuple[str, str]

Ask the entrance and return the state and its explanation.

paramcookiesstr
= ''

The Cookie header of a request the proxy signed in. Where the request with no credentials finds the proxy guarded, one with these is sent too.

Returns

tuple[str, str]
funccheck(fresh=False, cookies='') -> dict[str, Any]

Return the state of the proxy check: {"state", "detail", "checked"}.

paramfreshbool
= False

Ask the entrance now rather than return a kept result, and count of the forged headers browsers sent only those of the last FRESH_SECONDS, which is when the web app's test sent its own.

paramcookiesstr
= ''

The Cookie header of the request at hand, where a proxy signed it in; kept for the checks that follow.

Returns

dict

state is off when no header signs users in, else guarded,

func_view() -> dict[str, Any]

Return the kept result as callers see it.

Returns

dict[str, typing.Any]
func_store(key) -> None

Run the check and keep its result, logging a change of state.

paramkeytuple[str, ...]

Returns

None
func_renew() -> None

Renew a kept result in the background; a renewal that fails leaves the next request to ask.

Returns

None
funcbelieved(address, cookies='') -> bool

Whether the sign-in headers of a request that arrived from address with cookies count.

paramaddressstr
paramcookiesstr
= ''

Returns

bool