Access policy
Without an access policy, every device may open any connection to every other. With one, devices may open only the connections it passes, and everything else is refused.
How rules work #
The access policy is a set of rules in the network’s membership log. An admin sets it, like any other change, and from then on every device enforces it.
Each rule passes connections. This one:
pass from laptop to nas proto tcp port 445lets laptop open connections to port 445 on nas, and lets nas answer them. It does not let nas open anything on laptop. Anything no rule passes is refused. There are no rules that block, and the order of the rules does not matter.
The policy also decides who may use an exit node
or reach a subnet. Who may serve as one is
still decided by roles: juist grant vps exit makes vps an exit node, and
the policy says which devices may send their internet traffic through it.
Setting a policy #
Say the network has two laptops, a phone, a NAS at home, and a VPS that is its exit node and relay. Both laptops should be able to reach the NAS by ssh and SMB and to ping it, and the phone should reach its photos on port 443. The laptops go online through the VPS, and only laptop may log in to it.
Write the policy to a file, policy.conf:
table <laptops> { laptop, laptop2 }
pass from <laptops> to nas proto icmp proto tcp port { 22, 445 }
pass from phone to nas proto tcp port 443
pass from <laptops> to internet via vps
pass from laptop to vps proto tcp port 22
test pass from laptop2 to nas proto tcp port 445
test block from nas to laptop
test block from phone to laptopThe test lines say what the policy must pass and what it must refuse.
They are saved with the policy, and the policy is not set while any of them
fails.
Check it against the network, then set it:
$ juist policy check policy.conf
policy.conf: passes its 3 tests
$ juist policy set policy.conf
anything the policy does not pass is then refused; set it? [y/N] y
access policy set: 4 rules
juist asks only before setting the first policy; in a script, --yes
answers for you. If a change needs more than one admin, it waits for the
others as policy.rec (Several admins).
juist devices now lists laptop2, nas and vps, and ends with:
hint: 2 more devices the access policy keeps apart from this one: juist devices --all
No rule connects laptop2 with laptop or the phone, so it keeps no tunnel to
either. After juist exit use vps, laptop2 goes online through vps, and its DNS
goes through vps too.
juist policy prints the network’s policy. juist policy edit opens it in
$VISUAL or $EDITOR, or in vi if neither is set. When you save, it sets
the new policy, provided it parses and passes its tests. juist policy off
withdraws the policy, and every device may again open any connection to
every other.
The rule language #
Policies are written in the language of pf’s filter rules (pf is the BSD packet filter):
table <name> { laptop, phone, <other> }names a group of devices and tables.service web = proto tcp port { 80, 443 }names a set of ports.pass from SOURCE to DESTINATION, followed byproto,portandserviceas needed.
A source is a device, a <table>, any for every device, or several of
these in braces. A destination is any of those, or:
internet, through any exit node, orinternet via vps, through one device or table;- an address or prefix behind a subnet router, such as
192.168.1.0/24or192.168.1.20; name photos, a published name, given by its label under the network’s domain, orname @for the domain itself. The rule opens port 443, TCP and UDP, on the device that publishes the name, and only for that name. A rule withnamehas no other destination and no port. Members-only names need such a rule: a rule to their device, or toany, opens the port but not the name. If another device publishes the name later, the rule follows it.
proto takes tcp, udp, icmp or a list in braces. port takes a
port, a range such as 8000:8080, or a list. A rule can have several
proto clauses. A rule without proto, port or service passes
everything. Ping needs proto icmp, or a rule without a protocol.
pass multicast from SOURCE to DESTINATION passes
multicast and UDP broadcast from the sources to the
destinations, and nothing else. Its destinations are devices, tables or
any. A port limits it to those UDP ports, as in
pass multicast from <family> to <family> port 5353; it needs no proto,
since only UDP crosses; without a port, pings to IPv6 groups do too, as
ping ff02::1 sends them. With a port, a datagram too large for one packet
does not pass. The rule also passes the answers a destination
sends straight back to a group datagram, as SSDP sends them. The local
unicast copy for automatically copied or selected IPv4 groups needs ordinary UDP access to its
port as well. Rules that
pass multicast need the network’s schema 22, which
juist log upgrade raises.
A test starts with test pass or test block. It checks a connection from
one device to one destination: a device, internet via one exit node, an
address, or a name without a port. With a protocol and a port, it checks
exactly that connection; with only a protocol, any port of it; with neither,
whether anything passes at all. In a rule, by contrast, proto tcp passes
every TCP port.
A device whose name is a keyword, such as test, goes in double quotes:
"test". A rule continues onto the next line inside braces, or after a
\, as in pf. The policy has no comments, and # is an error: the log
stores the policy’s rules, not its text, so the reason for a rule belongs in
the commit message.
juist policy fmt prints a file in standard form, the way juist policy
prints the policy: lists sorted, and tables, services, rules and tests each
in their own block. It does not need a network.
How devices enforce it #
- Each device enforces the policy on the traffic it receives, and tracks the connections it passed so that replies get back. Even a device that ignores the policy gets nothing through. Devices also drop outgoing traffic they may not open themselves.
- An exit node forwards for a device only what the policy passes. A device the policy lets use an exit node may also use its DNS. A subnet router does the same for its subnet.
- A service member opens nothing, whatever the policy says. An ingress reaches what is published on a device, regardless of the policy.
- Two devices that no rule connects, in either direction, are not peers:
they keep no tunnel, and neither learns where the other can be reached.
juist devices,juist statusand the namesDEVICE.NETWORK.juistshow only the device itself and its peers. - Relays and vouchers reach every device for juist’s own traffic, since they carry the log. In a network with neither, every pair of devices keeps a tunnel, but only for juist’s own traffic.
- A device whose view of the network is stale keeps tunnels only to vouchers, as it would without a policy, and passes nothing but juist’s own traffic through them (freshness).
Why a connection does not get through #
Anything the policy refuses is dropped without a reply, so the connection
hangs until it times out. A device the policy keeps apart from this one does
not resolve as DEVICE.NETWORK.juist at all. juist policy test says what
the policy does with a connection, and which rule passes it:
$ juist policy test from laptop to nas proto tcp port 445
passes: pass from <laptops> to nas proto icmp proto tcp port { 22, 445 }
$ juist policy test from laptop to nas proto tcp port 8080
refused: no rule passes it
hint: laptop may open on nas: proto icmp proto tcp port { 22, 445 }
It takes the same arguments as a test line, and exits with status 1 if no
rule passes the connection.
What the policy does not hide #
Every device holds the whole log: every device’s name, keys and network
address, the roles, and the policy itself. Any member can read the whole
policy with juist policy. The policy stops devices from connecting, and
hides where a device can be reached from devices that are not its peers. It
does not hide which devices the network has.
Keeping the policy in git #
The file can live in a repository and be changed by merge request:
- CI runs
juist policy fmt -l policy.conf, which lists the file and fails if it is not in standard form.juist policy fmt -wrewrites it. juist policy check policy.confchecks it against the network and runs its tests. It needs the network’s log, so it has to run on a device in the network.- After the merge, an admin runs
juist policy set policy.conf. If the change needs more admins,policy.recgoes to them, andjuist approve policy.recshows the change as a diff against the current policy, along with any test it fails. juist policy diff policy.confprints both in standard form. Lines only the network’s policy has start with-, lines only the file has start with+, and lines both have are indented. It exits with status 1 if they differ, so it catches a change made withjuist policy editthat never went into git. If a device named in the file has since been removed or renamed, it fails at the line that names it.
Never give a CI job an admin key that reaches the quorum on its own, or anyone who can push to the repository could change the network.
Good to know #
juist policy setrefuses an empty file; to withdraw the policy, usejuist policy off.pass from any to anylets every device open anything on every other, but not use exit nodes, subnets or members-only names.- Under a policy,
juist inviteprintshint: the access policy passes phone nothing until it names it: juist policy edit. juist policy checkandsetwarn about anything that cannot work as written or leaves something unused: an exit node without the exit role, a prefix no subnet router routes, a device’s address where its name belongs, a service member or an ingress as a source, a device no rule names, an exit node or subnet router no rule uses, and a table or service no rule uses.juist policy set -reads the policy from standard input.juist loglists every change to the policy and who approved it;juist log show Nshows one as a diff.- A removed device drops out of every rule. A rule left without a source or
a destination is dropped, and so is any test that names the device.
juist removesays what the policy loses. juist devices --allalso lists the devices the policy keeps apart from this one, which an admin needs in order to rename or remove them.
Troubleshooting #
| You see | What to do |
|---|---|
juist: policy.conf:3: no device "nobody" in "home" | correct the name; juist devices --all lists the devices, and juist log says whether one was renamed or removed |
policy.conf:9: fails: test block from nas to laptop: pass from any to any passes it | change the rule that passes it, or the test |
juist: policy.conf:1: a policy has no comments: … | remove the comment and give the reason in the commit message |
juist: …: "test" is a word of the policy's; the device goes in double quotes: "test" | put the device’s name in double quotes |
| ssh or SMB hangs, then times out | juist policy test from laptop to nas proto tcp port 22 says whether the policy passes it |
nas.home.juist does not resolve | no rule connects this device and nas; check with juist policy test from laptop to nas |
| ping fails, ssh works | add proto icmp to the rule |
juist status: Exit node says vps, internet refused: the access policy does not let this device use it | add a rule pass from … to internet via vps |
juist subnet: not in use: the access policy passes none of it to this device | add a rule for an address or prefix in the subnet |