squid: base allow list for every authed user, and no start limit on the list reload
This commit is contained in:
parent
10fdf06334
commit
e78a529ec2
1 changed files with 82 additions and 12 deletions
|
|
@ -25,6 +25,7 @@ let
|
|||
denyCanary = "deny-canary.invalid";
|
||||
|
||||
allowed = lib.unique (cfg.allowedDomains ++ [ cfg.selfcheck.canary ]);
|
||||
base = lib.unique cfg.baseAllowedDomains;
|
||||
|
||||
# The squid user the self-check logs in as: `swarm_secret_client::proxy::SELFCHECK_USER`.
|
||||
selfcheckUser = "selfcheck.squid";
|
||||
|
|
@ -63,9 +64,9 @@ let
|
|||
# The IP-literal
|
||||
# denies come first because `dstdomain` on an IP literal does a reverse
|
||||
# lookup. The credential check sits right before the first allow, so only a
|
||||
# request that would otherwise be let through gets the 407 challenge. An
|
||||
# unrestricted user is then let through to any other destination on a safe
|
||||
# port, and every other user to the listed names. The `dst` denies cost an
|
||||
# request that would otherwise be let through gets the 407 challenge. Every
|
||||
# user is then let through to the base names, an unrestricted user to any other
|
||||
# destination on a safe port, and every other user to the listed names. The `dst` denies cost an
|
||||
# unlisted name a lookup on the host's resolver before it is refused.
|
||||
squidConf = ''
|
||||
http_port ${listen}
|
||||
|
|
@ -105,6 +106,7 @@ let
|
|||
# an agent controls could match an allowed name.
|
||||
acl ip_v4_host url_regex ^([a-z]+://)?[0-9.]+(:[0-9]+)?(/|$)
|
||||
acl ip_v6_host url_regex ^([a-z]+://)?\[[0-9a-fA-F:.]+\](:[0-9]+)?(/|$)
|
||||
${lib.optionalString (base != [ ]) "acl base_dst dstdomain ${lib.concatStringsSep " " base}"}
|
||||
${lib.optionalString (
|
||||
allowed != [ ]
|
||||
) "acl allowed_dst dstdomain ${lib.concatStringsSep " " allowed}"}
|
||||
|
|
@ -123,6 +125,7 @@ let
|
|||
http_access deny to_bridge
|
||||
http_access deny to_private
|
||||
http_access deny !authed
|
||||
${lib.optionalString (base != [ ]) "http_access allow base_dst"}
|
||||
http_access allow unrestricted
|
||||
${lib.optionalString (allowed != [ ]) "http_access deny !allowed_dst"}
|
||||
${lib.optionalString (allowed != [ ]) "http_access allow allowed_dst"}
|
||||
|
|
@ -225,13 +228,43 @@ in
|
|||
listed: they resolve to the bridge or a private address, which the
|
||||
destination rules refuse, and agents reach them directly. The
|
||||
self-check canary is allowed on top of this list, so it widens the
|
||||
restricted allow list by that one name. A name must resolve, on this host's resolver, to a public
|
||||
address: one in a loopback, link-local, private, shared (100.64.0.0/10),
|
||||
multicast or reserved range (or is `::`), or in the bridge subnet, gets a 403 for
|
||||
restricted allow list by that one name. A name must
|
||||
resolve, on this host's resolver, to a public address: one in a
|
||||
loopback, link-local, private, shared (100.64.0.0/10), multicast or
|
||||
reserved range (or is `::`), or in the bridge subnet, gets a 403 for
|
||||
every user.
|
||||
'';
|
||||
};
|
||||
|
||||
baseAllowedDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [
|
||||
"api.anthropic.com"
|
||||
"claude.ai"
|
||||
"platform.claude.com"
|
||||
];
|
||||
example = [
|
||||
"api.anthropic.com"
|
||||
"claude.ai"
|
||||
"platform.claude.com"
|
||||
"openrouter.ai"
|
||||
];
|
||||
description = ''
|
||||
Hostnames every authenticated user may reach, whether or not its agent
|
||||
holds the `unrestricted_internet` flag: what the agent harness needs
|
||||
off the swarm to work at all. The defaults are the hosts Claude Code
|
||||
requires. `api.anthropic.com` serves the model API and the usage
|
||||
probe, `claude.ai` the account login, and `platform.claude.com` the
|
||||
OAuth token exchange and refresh. An agent whose `claude` or ACP provider
|
||||
points elsewhere (`ANTHROPIC_BASE_URL`, an `opencode` provider) needs
|
||||
that host listed here: the value lives in the agent's own backend
|
||||
environment, which this host does not read. Swarm-internal names are
|
||||
not listed, since agents reach them directly. The destination denies
|
||||
still apply to every entry, and the list is separate from
|
||||
`allowedDomains`.
|
||||
'';
|
||||
};
|
||||
|
||||
usersFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/lib/hive-squid/users";
|
||||
|
|
@ -340,6 +373,9 @@ in
|
|||
|
||||
systemd.services.squid-unrestricted-reload = {
|
||||
description = "Reload squid to read its unrestricted-users list";
|
||||
# Several list writes in a few seconds must not trip the start limit: a
|
||||
# tripped limit also fails the path unit, and later writes then reload nothing.
|
||||
startLimitIntervalSec = 0;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${config.systemd.package}/bin/systemctl try-reload-or-restart squid.service";
|
||||
|
|
@ -348,10 +384,10 @@ in
|
|||
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(builtins.elem cfg.selfcheck.unrestrictedCanary allowed);
|
||||
assertion = !(builtins.elem cfg.selfcheck.unrestrictedCanary (allowed ++ base));
|
||||
message = ''
|
||||
services.hyperhive.squid.selfcheck.unrestrictedCanary must be a name outside
|
||||
allowedDomains, or the self-check cannot tell an unrestricted user from a
|
||||
allowedDomains and baseAllowedDomains, or the self-check cannot tell an unrestricted user from a
|
||||
restricted one.
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue