Watch
0
0
Fork
You've already forked hyperhive
0

squid blocks a runtime deny list from the controller in every egress mode, ahead of the allow list

This commit is contained in:
atlas 2026-11-04 22:00:42 +01:00 • committed by mara
commit dd5fde848f
2 changed files with 1814 additions and 24 deletions

View file

@ -299,6 +299,9 @@ in
HIVE_SQUID_ALLOWED_SEED_FILE =
config.services.hyperhive.deploy.hive-controller.squid.allowedSeedFile;
HIVE_SQUID_ALWAYS_ALLOWED = lib.concatStringsSep " " config.services.hyperhive.deploy.hive-controller.squid.alwaysAllowed;
# The swarm's egress deny list, which `workers::squid_deny` renders into the
# include squid reads before every allow.
HIVE_SQUID_DENY_FILE = config.services.hyperhive.deploy.hive-controller.squid.denyFile;
# The egress proxy facts every container is handed (`HiveFacts` in
# `hive-priv-sock`). They sit with the users file above because that is what
# makes squid's credential check and per-agent policy real on this host: an

View file

@ -10,6 +10,8 @@
# the swarm lists for that agent alone. A third kind, set per agent
# (`internet_log`), reaches the same names but is also let through to any other
# destination the deny rules leave; the request is logged as `would_deny`.
# The swarm's deny list (rendered by hive-c0re into `denyFile`) is refused for
# all three before any allow, and logged as `egress_denied`.
# An agent sends its credential
# once it has fetched it (../agent-modules/egress-proxy.nix); the exporter,
# journal forwarding and the self-check below show the proxy works.
@ -89,6 +91,10 @@ let
# `deny !allowed_dst`, so it sees the same denies and only turns that last
# refusal into an allow. The `dst` denies cost an unlisted name a lookup on
# the host's resolver before it is refused.
# The swarm's deny list (`denyFile`) sits right after those destination
# denies and before `deny !authed`, so a denied name is refused before any
# credential check and before every allow, in every mode: its rules name the
# destination first, and a swarm-wide one names no user at all.
squidConf = ''
http_port ${listen}
pid_filename /run/squid/squid.pid
@ -110,6 +116,14 @@ let
acl would_denied note would_deny 1
access_log syslog:daemon.info logformat=would_deny would_denied
# A request one of the deny list's rules (hive-c0re's `denyFile`, below)
# refused carries the annotation `egress_deny=1` and gets a line of its own,
# distinct from `would_deny` and from the 403 line every refusal has, in
# every mode.
logformat egress_denied egress_denied user=%[un method=%rm host=%>rd port=%>rP result=%Ss/%03>Hs
acl egress_denied_hit note egress_deny 1
access_log syslog:daemon.info logformat=egress_denied egress_denied_hit
# The helper reads the users file on every request and reloads it when its
# mtime moves. It exits when the file is missing, which is why the file is
# created before squid starts. A positive answer is cached for a minute, so
@ -153,6 +167,7 @@ let
http_access deny to_linklocal
http_access deny to_bridge
http_access deny to_private
include ${cfg.denyFile}
http_access deny !authed
${lib.optionalString (base != [ ]) "http_access allow base_dst"}
include ${cfg.agentAllowFile}
@ -162,9 +177,9 @@ let
http_access deny all
'';
# `squid -k parse` on the configuration with the include pointing at an empty
# `squid -k parse` on the configuration with the includes pointing at an empty
# file. The nixpkgs module runs the same parse on the real text, which
# cannot open the include in the build sandbox.
# cannot open the includes in the build sandbox.
squidConfCheck =
pkgs.runCommand "squid-conf-check"
{
@ -173,7 +188,7 @@ let
''
squid -k parse -f ${
pkgs.writeText "squid.conf" (
builtins.replaceStrings [ cfg.agentAllowFile ] [ "/dev/null" ] squidConf
builtins.replaceStrings [ cfg.agentAllowFile cfg.denyFile ] [ "/dev/null" "/dev/null" ] squidConf
)
}
touch $out
@ -381,6 +396,24 @@ in
'';
};
denyFile = lib.mkOption {
type = lib.types.path;
default = "${builtins.dirOf cfg.usersFile}/deny.conf";
readOnly = true;
defaultText = lib.literalMD "`deny.conf`, in the directory of `usersFile`";
description = ''
Squid configuration that hive-c0re generates from the swarm's deny list
(`/api/egress/denied-domains`) and the main configuration includes after
the destination denies and before `deny !authed` and every allow: a
`http_access deny` for the swarm-wide names, and one per agent of this
hive the swarm lists names for. A denied name is refused for every user
in every egress mode, whether or not an allow rule covers it. The file
is empty, and squid configured as without it, until the swarm lists a
name. Squid is reloaded when it changes. Owned by `hive-core` and readable
by the `squid` group.
'';
};
allowedSeedFile = lib.mkOption {
type = lib.types.path;
default = seedFile;
@ -481,6 +514,7 @@ in
"f ${cfg.unrestrictedFile} 0640 hive-core squid - -"
"C ${cfg.allowedFile} 0640 hive-core squid - ${seedFile}"
"f ${cfg.agentAllowFile} 0640 hive-core squid - -"
"f ${cfg.denyFile} 0640 hive-core squid - -"
];
# Squid reads the unrestricted list, the allow-list and the per-agent include
@ -494,6 +528,7 @@ in
cfg.unrestrictedFile
cfg.allowedFile
cfg.agentAllowFile
cfg.denyFile
];
};