Compare commits
2 changed files with 14 additions and 127 deletions
|
|
@ -450,8 +450,7 @@ in
|
||||||
"${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
|
"${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
|
||||||
listen = gatewayCfg.lib.listen;
|
listen = gatewayCfg.lib.listen;
|
||||||
extraConfig = gatewayCfg.lib.securityHeaders;
|
extraConfig = gatewayCfg.lib.securityHeaders;
|
||||||
locations = {
|
locations."/" = {
|
||||||
"/" = {
|
|
||||||
proxyPass = "http://127.0.0.1:${toString cfg.httpPort}/";
|
proxyPass = "http://127.0.0.1:${toString cfg.httpPort}/";
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
|
|
@ -461,44 +460,6 @@ in
|
||||||
proxy_send_timeout 1h;
|
proxy_send_timeout 1h;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# ⚠️ EXACT match, and that is what makes this safe. Forgejo
|
|
||||||
# serves `/metrics` on the same listener the `/` prefix above
|
|
||||||
# already proxies, so without a more specific location the
|
|
||||||
# endpoint would ride that catch-all to anyone. `= /metrics`
|
|
||||||
# outranks the `/` prefix in nginx, so this location — and its
|
|
||||||
# auth — is the one that runs.
|
|
||||||
"= /metrics" = {
|
|
||||||
proxyPass = "http://127.0.0.1:${toString cfg.httpPort}/metrics";
|
|
||||||
extraConfig = ''
|
|
||||||
auth_request /__forge_metrics_authz;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# The subrequest. Same implementation and header set as
|
|
||||||
# `swarm-ui.nix` uses, for the same reason: `X-Original-URL`
|
|
||||||
# and `X-Original-Method` are what authelia's `auth-request`
|
|
||||||
# implementation reads.
|
|
||||||
#
|
|
||||||
# ⚠️ NO `error_page 401 =302` here, and its absence is the
|
|
||||||
# whole point. The swarm UI redirects an unauthenticated
|
|
||||||
# browser to a login page; a scraper handed that 302 would
|
|
||||||
# follow it and parse an HTML page as metrics. A machine-facing
|
|
||||||
# location lets the 401 reach the client unchanged.
|
|
||||||
"= /__forge_metrics_authz" = {
|
|
||||||
proxyPass = "http://127.0.0.1:${toString autheliaCfg.port}/api/authz/auth-request";
|
|
||||||
extraConfig = ''
|
|
||||||
internal;
|
|
||||||
proxy_pass_request_body off;
|
|
||||||
proxy_set_header Content-Length "";
|
|
||||||
proxy_set_header X-Original-Method $request_method;
|
|
||||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -713,20 +674,6 @@ in
|
||||||
DEFAULT_BRANCH = "main";
|
DEFAULT_BRANCH = "main";
|
||||||
DEFAULT_PRIVATE = "private";
|
DEFAULT_PRIVATE = "private";
|
||||||
};
|
};
|
||||||
# Not an option: a swarm-integrated, auto-deployed forge
|
|
||||||
# always has metrics. Tied to `behindGateway` because that
|
|
||||||
# IS the swarm-integrated shape — it is the condition under
|
|
||||||
# which the protected `= /metrics` location below exists.
|
|
||||||
# Serving the endpoint without that location would put it on
|
|
||||||
# a listener `openFirewall` can expose, with nothing in
|
|
||||||
# front of it.
|
|
||||||
#
|
|
||||||
# No `TOKEN` here on purpose. Forgejo can guard this itself
|
|
||||||
# with a static bearer, but the swarm authenticates the
|
|
||||||
# scraper at the gateway, so a second credential system per
|
|
||||||
# service would buy nothing and would be the one that stops
|
|
||||||
# getting rotated.
|
|
||||||
metrics.ENABLED = cfg.behindGateway;
|
|
||||||
# Repo migrations / pull-mirrors fetch from the source
|
# Repo migrations / pull-mirrors fetch from the source
|
||||||
# URL *inside* Forgejo. hyperhive code is synced from
|
# URL *inside* Forgejo. hyperhive code is synced from
|
||||||
# `localhost` (and the host LAN), which Forgejo's
|
# `localhost` (and the host LAN), which Forgejo's
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ let
|
||||||
hyperhiveDomain = hyperhiveCfg.domain;
|
hyperhiveDomain = hyperhiveCfg.domain;
|
||||||
swarmDomain = hyperhiveCfg.swarm.domain;
|
swarmDomain = hyperhiveCfg.swarm.domain;
|
||||||
uiCfg = hyperhiveCfg.swarm.ui;
|
uiCfg = hyperhiveCfg.swarm.ui;
|
||||||
forgeCfg = hyperhiveCfg.swarm.forge;
|
|
||||||
|
|
||||||
# Group an account must hold to reach operator-only surfaces. Named
|
# Group an account must hold to reach operator-only surfaces. Named
|
||||||
# here because this module writes the rule that enforces it and
|
# here because this module writes the rule that enforces it and
|
||||||
|
|
@ -1079,35 +1078,6 @@ in
|
||||||
settings = {
|
settings = {
|
||||||
theme = "dark";
|
theme = "dark";
|
||||||
server.address = "tcp://127.0.0.1:${toString cfg.port}";
|
server.address = "tcp://127.0.0.1:${toString cfg.port}";
|
||||||
|
|
||||||
# Let a machine present an OAuth2 access token to the same
|
|
||||||
# `auth_request` endpoint browsers use, so a scraper can be
|
|
||||||
# authenticated by the gateway instead of every service
|
|
||||||
# growing its own static bearer.
|
|
||||||
#
|
|
||||||
# ⚠️ `authn_strategies` REPLACES the defaults rather than
|
|
||||||
# adding to them, so `CookieSession` is listed explicitly.
|
|
||||||
# Dropping it does not fail to evaluate and does not fail to
|
|
||||||
# start — it silently ends every operator session on the
|
|
||||||
# swarm UI, which rides this same endpoint.
|
|
||||||
#
|
|
||||||
# Unconditional, and not keyed to whichever service is
|
|
||||||
# currently scraped: this only makes a *scheme* available.
|
|
||||||
# Authorisation is the audience — authelia refuses a token
|
|
||||||
# that carries no audience for the requested URL, and a
|
|
||||||
# client may only be issued audiences it is registered for.
|
|
||||||
# So enabling the scheme grants nobody anything until a
|
|
||||||
# client is registered for a specific URL.
|
|
||||||
server.endpoints.authz.auth-request = {
|
|
||||||
implementation = "AuthRequest";
|
|
||||||
authn_strategies = [
|
|
||||||
{
|
|
||||||
name = "HeaderAuthorization";
|
|
||||||
schemes = [ "Bearer" ];
|
|
||||||
}
|
|
||||||
{ name = "CookieSession"; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
log.level = "info";
|
log.level = "info";
|
||||||
|
|
||||||
# `watch` is load-bearing, not a convenience: authelia reads
|
# `watch` is load-bearing, not a convenience: authelia reads
|
||||||
|
|
@ -1135,37 +1105,7 @@ in
|
||||||
# the account to disagree silently.
|
# the account to disagree silently.
|
||||||
access_control = {
|
access_control = {
|
||||||
default_policy = "one_factor";
|
default_policy = "one_factor";
|
||||||
# ⚠️ ORDER MATTERS — authelia takes the FIRST matching rule.
|
rules = lib.optional uiCfg.enable {
|
||||||
# The metrics rule is listed first so it cannot be shadowed
|
|
||||||
# by a broader domain rule added later.
|
|
||||||
rules =
|
|
||||||
# The forge's metrics endpoint. `deny` is deliberate and
|
|
||||||
# is the whole protection right now: the endpoint is
|
|
||||||
# always served (a swarm-integrated forge always has
|
|
||||||
# metrics), and `default_policy` is `one_factor`, which
|
|
||||||
# means *any* authenticated subject — every operator
|
|
||||||
# today, every agent once they hold authelia accounts.
|
|
||||||
#
|
|
||||||
# Being reachable by a Bearer token is not sufficient on
|
|
||||||
# its own: `authn_strategies` on this endpoint also
|
|
||||||
# accepts `CookieSession`, and a cookie carries no
|
|
||||||
# audience, so the audience is not what stands between a
|
|
||||||
# browser session and this data.
|
|
||||||
#
|
|
||||||
# The collector gets in by REPLACING this with a
|
|
||||||
# client-scoped allow (`subject = ["oauth2:client:<id>"]`)
|
|
||||||
# once such a client is registered. Denying until then is
|
|
||||||
# what makes publishing the endpoint safe on its own —
|
|
||||||
# authelia refuses a subject naming a client that is not
|
|
||||||
# registered, and it does so in a `preStart` validator,
|
|
||||||
# so naming one early takes the whole SSO service down on
|
|
||||||
# the next restart rather than failing the build.
|
|
||||||
lib.optional forgeCfg.behindGateway {
|
|
||||||
domain = forgeCfg.domain;
|
|
||||||
resources = [ "^/metrics$" ];
|
|
||||||
policy = "deny";
|
|
||||||
}
|
|
||||||
++ lib.optional uiCfg.enable {
|
|
||||||
domain = uiCfg.domain;
|
domain = uiCfg.domain;
|
||||||
subject = [ "group:${operatorGroup}" ];
|
subject = [ "group:${operatorGroup}" ];
|
||||||
policy = "one_factor";
|
policy = "one_factor";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue