send agent traffic through squid once its hive announces it
This commit is contained in:
parent
86e9f49fe6
commit
b551eb02ff
2 changed files with 885 additions and 27 deletions
|
|
@ -3,7 +3,9 @@
|
|||
# not executes").
|
||||
#
|
||||
# The agent side of the swarm-minted forge token: ../agent-modules/forge-token.nix
|
||||
# fetches it, and ../agent-modules/forge.nix's readers find it.
|
||||
# fetches it, and ../agent-modules/forge.nix's readers find it. The same fixtures
|
||||
# cover ../agent-modules/egress-proxy.nix, the agent's proxy credential and the
|
||||
# environment built from it.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
|
@ -31,6 +33,8 @@ let
|
|||
agentForgeBao = agentWith {
|
||||
services.hyperhive.agent.bao.addr = baoAddr;
|
||||
services.hyperhive.agent.forge.url = forgeUrl;
|
||||
# A second `EnvironmentFile=` on the harness, which the proxy file must join.
|
||||
services.hyperhive.agent.backendEnvironmentFile = "/var/lib/t/backend.env";
|
||||
services.hyperhive.agent.icon = pkgs.emptyFile;
|
||||
};
|
||||
|
||||
|
|
@ -42,8 +46,22 @@ let
|
|||
};
|
||||
|
||||
fetchUnit = machine: machine.systemd.services.hive-agent-forge-token;
|
||||
proxyUnit = machine: machine.systemd.services.hive-agent-proxy-credential;
|
||||
accountsUnit = machine: machine.systemd.services.hive-agent-forge-accounts;
|
||||
tokenFile = machine: machine.services.hyperhive.agent.forge.tokenFile;
|
||||
|
||||
proxyEnvEntry = "-/run/hive-agent-proxy-credential/proxy.env";
|
||||
# The units that make outbound calls and so load the proxy file. This fixture
|
||||
# has an icon, a forge and GitHub on, and no matrix accounts.
|
||||
proxyConsumers = [
|
||||
"hive-agent"
|
||||
"hive-bash-daemon"
|
||||
"hive-subagent-daemon"
|
||||
"hive-mcp-http"
|
||||
"hive-forge-notify"
|
||||
"hive-github-notify"
|
||||
"forge-avatar-sync"
|
||||
];
|
||||
in
|
||||
let
|
||||
cases = [
|
||||
|
|
@ -238,6 +256,137 @@ let
|
|||
name = "the account fetch re-runs every two minutes";
|
||||
ok = agentForgeBao.systemd.timers.hive-agent-forge-accounts.timerConfig.OnUnitInactiveSec == "2min";
|
||||
}
|
||||
{
|
||||
# ../agent-modules/egress-proxy.nix. Presence arm: the unit and its timer
|
||||
# exist, and the agent is told nothing until a credential is fetched.
|
||||
name = "an agent with a store address fetches its egress-proxy credential, and one without does not";
|
||||
ok =
|
||||
agentForgeBao.systemd.services ? hive-agent-proxy-credential
|
||||
&& agentForgeBao.systemd.timers ? hive-agent-proxy-credential
|
||||
&& !(agentForgeNoBao.systemd.services ? hive-agent-proxy-credential)
|
||||
&& !(agentForgeNoBao.systemd.timers ? hive-agent-proxy-credential);
|
||||
}
|
||||
{
|
||||
# The nix half of `swarm_secret_client::proxy::agent_credential_path`
|
||||
# plus the mount.
|
||||
name = "the proxy fetch reads the agent's own proxy-credential path, by redirect, and never echoes it";
|
||||
ok =
|
||||
let
|
||||
name = agentForgeBao.services.hyperhive.agent.user.name;
|
||||
s = (proxyUnit agentForgeBao).script;
|
||||
in
|
||||
lib.hasInfix "secret/swarm/agents/${name}/proxy-credential" s
|
||||
&& lib.hasInfix "-field=value" s
|
||||
&& lib.hasInfix "> ${lib.escapeShellArg "/run/hive-agent-proxy-credential/secret.new"}" s
|
||||
&& !(lib.hasInfix "echo \"$(bao" s)
|
||||
&& !(lib.hasInfix "echo $(bao" s)
|
||||
&& !(lib.hasInfix "echo \"$secret" s)
|
||||
&& !(lib.hasInfix "printf '%s\\n' \"$secret" s);
|
||||
}
|
||||
{
|
||||
name = "the proxy fetch runs as the agent with its own store identity and keeps its file between runs";
|
||||
ok =
|
||||
let
|
||||
u = proxyUnit agentForgeBao;
|
||||
c = u.serviceConfig;
|
||||
in
|
||||
c.User == agentForgeBao.services.hyperhive.agent.user.name
|
||||
&& c.UMask == "0177"
|
||||
&& !c.RemainAfterExit
|
||||
&& c.RuntimeDirectory == "hive-agent-proxy-credential"
|
||||
&& c.RuntimeDirectoryMode == "0700"
|
||||
&& c.RuntimeDirectoryPreserve == "yes"
|
||||
&& u.environment.BAO_ADDR == baoAddr
|
||||
&& u.environment.BAO_CLIENT_CERT == "/run/hive-agent-bao-identity/current/cert";
|
||||
}
|
||||
{
|
||||
# Presence: every outbound unit loads the file, and the harness keeps the
|
||||
# entry it already had. Absence: an agent with no store loads none.
|
||||
name = "every outbound unit loads the proxy file optionally, beside its other environment files";
|
||||
ok =
|
||||
let
|
||||
svc = agentForgeBao.systemd.services;
|
||||
files = c: svc.${c}.serviceConfig.EnvironmentFile or [ ];
|
||||
in
|
||||
lib.all (
|
||||
c:
|
||||
lib.elem proxyEnvEntry (files c)
|
||||
&& lib.elem "hive-agent-proxy-credential.service" svc.${c}.after
|
||||
&& lib.elem "hive-agent-proxy-credential.service" svc.${c}.wants
|
||||
&& !(lib.elem "hive-agent-proxy-credential.service" (svc.${c}.requires or [ ]))
|
||||
) proxyConsumers
|
||||
&& lib.elem "-/var/lib/t/backend.env" (files "hive-agent")
|
||||
&& !(lib.any
|
||||
(
|
||||
c:
|
||||
lib.elem proxyEnvEntry (agentForgeNoBao.systemd.services.${c}.serviceConfig.EnvironmentFile or [ ])
|
||||
)
|
||||
[
|
||||
"hive-agent"
|
||||
"hive-bash-daemon"
|
||||
"hive-forge-notify"
|
||||
]
|
||||
);
|
||||
}
|
||||
{
|
||||
# The credential is in a file only the fetch writes. Nothing static (a
|
||||
# unit environment, the global one, the login shells) carries a proxy
|
||||
# variable, so an agent without a fetched credential goes direct.
|
||||
name = "no proxy variable is set where the credential is not";
|
||||
ok =
|
||||
let
|
||||
names = [
|
||||
"HTTPS_PROXY"
|
||||
"HTTP_PROXY"
|
||||
"https_proxy"
|
||||
"http_proxy"
|
||||
"NO_PROXY"
|
||||
"no_proxy"
|
||||
"NODE_USE_ENV_PROXY"
|
||||
];
|
||||
anywhere =
|
||||
m: n:
|
||||
m.systemd.globalEnvironment ? ${n}
|
||||
|| m.environment.variables ? ${n}
|
||||
|| lib.any (s: s.environment ? ${n}) (lib.attrValues m.systemd.services);
|
||||
in
|
||||
!(lib.any (anywhere agentForgeBao) names) && !(lib.any (anywhere agentForgeNoBao) names);
|
||||
}
|
||||
{
|
||||
# The hive's facts are the only source of the proxy address and of the
|
||||
# hosts that skip it, and they are read before the store is asked: a hive
|
||||
# without them gets no login and no file. The loopback names stay local.
|
||||
name = "the proxy file takes the proxy and the direct hosts from the hive's facts, before it asks the store";
|
||||
ok =
|
||||
let
|
||||
s = (proxyUnit agentForgeBao).script;
|
||||
at = needle: lib.stringLength (lib.head (lib.splitString needle s));
|
||||
in
|
||||
lib.hasInfix "HIVE_SQUID_PROXY) proxy=$value" s
|
||||
&& lib.hasInfix "HIVE_EGRESS_NO_PROXY) direct=$value" s
|
||||
&& lib.hasInfix "no_proxy=localhost,127.0.0.1,::1,$direct" s
|
||||
&& lib.hasInfix "/etc/hyperhive-hive-facts" s
|
||||
&& at "HIVE_SQUID_PROXY)" < at "bao login"
|
||||
&& !(lib.hasInfix "hyperhive-bridge-dns" s);
|
||||
}
|
||||
{
|
||||
name = "the proxy file names every variable the clients read, and the credential only in the URL's userinfo";
|
||||
ok =
|
||||
let
|
||||
s = (proxyUnit agentForgeBao).script;
|
||||
name = agentForgeBao.services.hyperhive.agent.user.name;
|
||||
in
|
||||
lib.all (v: lib.hasInfix "printf '${v}=%s\\n'" s) [
|
||||
"HTTPS_PROXY"
|
||||
"HTTP_PROXY"
|
||||
"https_proxy"
|
||||
"http_proxy"
|
||||
"NO_PROXY"
|
||||
"no_proxy"
|
||||
]
|
||||
&& lib.hasInfix "NODE_USE_ENV_PROXY=1" s
|
||||
&& lib.hasInfix "url=\"http://${name}:$secret@$proxy\"" s;
|
||||
}
|
||||
];
|
||||
in
|
||||
runGroup "agent-forge-bao" cases
|
||||
|
|
|
|||
|
|
@ -622,6 +622,68 @@ let
|
|||
builtins.elem 3128 fw.interfaces.${hiveOtel.services.hyperhive.network.bridgeName}.allowedTCPPorts
|
||||
&& !(builtins.elem 3128 fw.allowedTCPPorts);
|
||||
}
|
||||
{
|
||||
# The facts that tell an agent this host's squid checks its credential and
|
||||
# applies its flag. Presence arm: a hive holding a store identity writes
|
||||
# both, and the direct hosts carry the names the brief's destinations
|
||||
# resolve under. Absence arm: a hive with no identity renders no users
|
||||
# file, so it announces no proxy.
|
||||
name = "hive-c0re hands agents the squid address and the hosts that skip it, only with a store identity";
|
||||
ok =
|
||||
let
|
||||
e = hiveOtelAllLocal.systemd.services.hive-c0re.environment;
|
||||
s = hiveOtelAllLocal.services.hyperhive;
|
||||
hosts = lib.splitString "," e.HIVE_EGRESS_NO_PROXY;
|
||||
in
|
||||
e.HIVE_SQUID_PROXY == "${s.network.bridgeIp}:3128"
|
||||
&& e.HIVE_SQUID_PROXY == s.squid.listen
|
||||
&& lib.all (h: lib.elem h hosts) (
|
||||
[
|
||||
s.network.bridgeIp
|
||||
s.swarm.domain
|
||||
s.swarm.bao.domain
|
||||
s.swarm.nats.domain
|
||||
s.swarm.attic.domain
|
||||
s.swarm.forge.domain
|
||||
s.swarm.matrix.gatewayHost
|
||||
s.swarm.victorialogs.domain
|
||||
s.swarm.victoriametrics.domain
|
||||
s.swarm.otel.domain
|
||||
s.otel.collector.host
|
||||
s.domain
|
||||
]
|
||||
++ s.gateway.localNames
|
||||
)
|
||||
&& !(lib.elem "example.com" hosts)
|
||||
&& lib.length hosts == lib.length (lib.unique hosts)
|
||||
&& !(hiveOtelBare.systemd.services.hive-c0re.environment ? HIVE_SQUID_PROXY)
|
||||
&& !(hiveOtelBare.systemd.services.hive-c0re.environment ? HIVE_EGRESS_NO_PROXY);
|
||||
}
|
||||
{
|
||||
# The store and the queue are in no list the gateway builds, so a hive whose
|
||||
# store is elsewhere gets them from their own options. Control: this
|
||||
# fixture runs no store, so the gateway does not name it.
|
||||
name = "a hive without a local store still lists the store and the queue as direct hosts";
|
||||
ok =
|
||||
let
|
||||
s = hiveOtelLeaf.services.hyperhive;
|
||||
hosts = lib.splitString "," hiveOtelLeaf.systemd.services.hive-c0re.environment.HIVE_EGRESS_NO_PROXY;
|
||||
in
|
||||
!(lib.elem s.swarm.bao.domain s.gateway.localNames)
|
||||
&& lib.elem s.swarm.bao.domain hosts
|
||||
&& lib.elem s.swarm.nats.domain hosts;
|
||||
}
|
||||
{
|
||||
# Squid's listener and the fact are one value.
|
||||
name = "the squid fact is the address squid binds";
|
||||
ok =
|
||||
let
|
||||
s = hiveOtelAllLocal.services.hyperhive;
|
||||
in
|
||||
lib.elem "http_port ${s.squid.listen}" (
|
||||
lib.splitString "\n" hiveOtelAllLocal.services.squid.configText
|
||||
);
|
||||
}
|
||||
];
|
||||
in
|
||||
runGroup "hive-otel" cases
|
||||
|
|
|
|||
Loading…
Reference in a new issue