feat(#3517): a named capability for authelia's bearer-authz scope
A scraper reaches a service published behind the gateway by presenting an access token to authelia's authz endpoint, which requires the client to carry authelia.bearer.authz. Nothing could express that: scopes are derived from kind, and a machine client rendered an empty list. A named capability rather than a free-form scopes list, for the reason the derivation exists — authelia refuses some scope/grant combinations outright, openid with client_credentials among them, and a list makes those expressible again. The two assertions carry their weight: authelia checks the same obligations, but in its preStart validator, so a violation builds and deploys cleanly and then fails to restart with swarm SSO attached to it.
This commit is contained in:
parent
32c5973956
commit
da88d450dd
1 changed files with 77 additions and 1 deletions
|
|
@ -217,7 +217,7 @@ let
|
||||||
if c.kind == "machine" then
|
if c.kind == "machine" then
|
||||||
''
|
''
|
||||||
printf -- ' grant_types: ["client_credentials"]\n'
|
printf -- ' grant_types: ["client_credentials"]\n'
|
||||||
printf -- ' scopes: []\n'
|
printf -- ' scopes: [${lib.optionalString c.bearerAuthz "authelia.bearer.authz"}]\n'
|
||||||
''
|
''
|
||||||
else
|
else
|
||||||
''
|
''
|
||||||
|
|
@ -575,6 +575,34 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bearerAuthz = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
example = true;
|
||||||
|
description = ''
|
||||||
|
Grant this client the `authelia.bearer.authz` scope, so it
|
||||||
|
may present its access token to authelia's authz endpoint
|
||||||
|
and be authorised by an `access_control` rule — how a
|
||||||
|
scraper reaches a service published behind the gateway.
|
||||||
|
|
||||||
|
A named capability rather than a free-form `scopes` list,
|
||||||
|
for the same reason `kind` derives the rest: authelia
|
||||||
|
refuses some scope/grant combinations outright (`openid`
|
||||||
|
with `client_credentials` among them), and a list would
|
||||||
|
make those combinations expressible again. This admits the
|
||||||
|
one value that is legal here and nothing else.
|
||||||
|
|
||||||
|
::: {.note}
|
||||||
|
Setting this obliges two other options, and the assertions
|
||||||
|
below enforce it. Authelia checks the same thing, but only
|
||||||
|
in its `preStart` validator — which means a violation
|
||||||
|
builds and deploys cleanly and then fails to restart,
|
||||||
|
taking swarm SSO down. The assertions move that to
|
||||||
|
evaluation, where a wrong value costs nothing.
|
||||||
|
:::
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
accessTokenSignedResponseAlg = lib.mkOption {
|
accessTokenSignedResponseAlg = lib.mkOption {
|
||||||
type = lib.types.nullOr (
|
type = lib.types.nullOr (
|
||||||
lib.types.enum [
|
lib.types.enum [
|
||||||
|
|
@ -820,6 +848,54 @@ in
|
||||||
+ "services.hyperhive.swarm.hives; rename the hive or the "
|
+ "services.hyperhive.swarm.hives; rename the hive or the "
|
||||||
+ "colliding client.";
|
+ "colliding client.";
|
||||||
}
|
}
|
||||||
|
# The two obligations `authelia.bearer.authz` carries. Authelia
|
||||||
|
# enforces both itself — but in its `preStart` validator, so a
|
||||||
|
# violation produces a green `nixos-rebuild switch` and an authelia
|
||||||
|
# that then refuses to come back up, taking swarm SSO with it.
|
||||||
|
# Asserting here moves the same failure to evaluation, where a
|
||||||
|
# wrong value costs a build and nothing else.
|
||||||
|
{
|
||||||
|
assertion = lib.all (c: !c.bearerAuthz || c.audience != [ ]) cfg.oidc.clients;
|
||||||
|
message =
|
||||||
|
"services.hyperhive.swarm.authelia.oidc.clients: "
|
||||||
|
+ lib.concatStringsSep ", " (
|
||||||
|
map (c: "client '${c.id}'") (lib.filter (c: c.bearerAuthz && c.audience == [ ]) cfg.oidc.clients)
|
||||||
|
)
|
||||||
|
+ " sets bearerAuthz but declares no audience. The audience is "
|
||||||
|
+ "what authorises a bearer token at a given URL, so without "
|
||||||
|
+ "one the scope grants access to nothing and authelia refuses "
|
||||||
|
+ "the configuration outright.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = lib.all (
|
||||||
|
c:
|
||||||
|
!c.bearerAuthz
|
||||||
|
|| lib.elem c.tokenEndpointAuthMethod [
|
||||||
|
"client_secret_basic"
|
||||||
|
"client_secret_jwt"
|
||||||
|
"private_key_jwt"
|
||||||
|
]
|
||||||
|
) cfg.oidc.clients;
|
||||||
|
message =
|
||||||
|
"services.hyperhive.swarm.authelia.oidc.clients: "
|
||||||
|
+ lib.concatStringsSep ", " (
|
||||||
|
map (c: "client '${c.id}' (tokenEndpointAuthMethod = ${toString c.tokenEndpointAuthMethod})") (
|
||||||
|
lib.filter (
|
||||||
|
c:
|
||||||
|
c.bearerAuthz
|
||||||
|
&& !lib.elem c.tokenEndpointAuthMethod [
|
||||||
|
"client_secret_basic"
|
||||||
|
"client_secret_jwt"
|
||||||
|
"private_key_jwt"
|
||||||
|
]
|
||||||
|
) cfg.oidc.clients
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ ". A confidential client carrying authelia.bearer.authz must "
|
||||||
|
+ "authenticate with client_secret_basic, client_secret_jwt or "
|
||||||
|
+ "private_key_jwt. Notably client_secret_post is refused, and "
|
||||||
|
+ "it is what an OAuth2 client library may reach for first.";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# Authelia's own gateway surface: the vhost that fronts it and the
|
# Authelia's own gateway surface: the vhost that fronts it and the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue