Enrolment turns a fresh node into a managed one: it exchanges a one-time
code for a long-lived node token, stores it in its data directory, and
opens its link to opdns.
In the dashboard, open the profile’s Nodes page and click Add a
node. Name it and click Create enrolment code.
You get a code that starts with opdnsenrol_. It works once and
expires after 15 minutes. The dialog also shows the commands below
with the code filled in.
Screenshot to comeThe dashboard's Enrol dialog with the enrolment code and copy buttons for the Docker and binary commands.
The data directory gets node.json (mode 0600): the node id, the node token
(opdnsnode_…), the profile id and the link and profile URLs the cloud
handed out. The one-time code is not stored, only its hash, so enrolling
again with the same code is a no-op instead of an error. It also gets
profile_keys.json: the keys the cloud signs your profile with, which the
node checks every profile against from then on
(Profile signing keys).
The node token is the node’s only credential, so it does not live
forever. A connected node rotates it by itself every 90 days
(link.token_rotation; 0 turns this off): over its link it asks the
cloud for a new token, writes it to node.json (atomically, so a power
cut leaves either the old or the new token), switches to it, and confirms.
The previous token keeps working for 10 minutes after that, so nothing
in flight breaks. Each rotation is audited (node.token_rotated, shown
as Node token rotated in the dashboard’s activity lists), and the
node’s record carries the time of the last one (token_rotated_at in the
API).
To rotate now, for example after the data directory was copied somewhere
it should not be:
Terminal window
sudo-uopdnsopdns-noderotate-token
rotated the token of node 7c9e…; the new token is saved in /var/lib/opdns/node.json
the previous token keeps working for 10m0s; a running node switches to the new one within seconds
It works whether or not the node is running: a running node picks up the
new token from node.json within about 10 seconds and reconnects with it.
Only the current token can rotate; the previous one, during its 10
minutes, is refused with node_token_superseded. The local page shows
when the token was issued, how often it rotates and the last failed
attempt, if any. A token you think has leaked is better handled by
Revoke on the Nodes page, which stops
it at once.
To move a node to new hardware or a new container without a new code, keep
the data directory (it has the token, the last profile and the logs), or
enrol the new install with the old node token from node.json:
Terminal window
opdns-nodeenrol--tokenopdnsnode_...
The node asks the cloud which node the token belongs to. With
--node-id <id> it skips that and enrols offline.
A node that already runs in standalone mode
(a profile_file, or an inline profile: in its config) sends that local
profile along with the enrolment code, so your dashboard profile starts out
as a copy of what the node was doing:
Terminal window
sudo-uopdnsopdns-nodeenrol--tokenopdnsenrol_...
Before sending anything it shows what it will upload and asks:
enrolling uploads the local profile (profile_file /etc/opdns/profile.yaml) and replaces the dashboard profile of this code with it:
"Home": 3 deny and 1 allow rules, 1 rewrites, 2 lists, block mode nxdomain, logs on
the cloud refuses it when that profile already has settings or rules; --keep-cloud-profile enrols without uploading
replace the dashboard profile with the local profile? [y/N]
Answering anything but y cancels: nothing is sent and the code stays
valid. Off a terminal (a script, a provisioning tool) the question cannot
be asked, so the command refuses unless you pass --yes. Scripts that
enrolled a standalone node without it need the flag added.
After the upload:
the local profile (profile_file /etc/opdns/profile.yaml) is now the cloud profile: edit it in the dashboard from here on
the local profile is archived in /var/lib/opdns/archive/standalone-profile-<time>.json; the node now pulls the cloud profile
note: profile_file is no longer read (the node follows the cloud profile); remove it from the config
The uploaded profile is kept in <data_dir>/archive/, and node.json
records where it came from.
The cloud takes the local profile only while its own profile is untouched
(never edited, no rules). Otherwise the enrolment is refused with
profile_not_empty: nothing is enrolled and the code stays valid. Then
either
enrol with --keep-cloud-profile to leave the local profile out, so the
node follows the dashboard’s profile as it is; or
reset the profile in the dashboard and enrol again.
Only a fresh enrolment code uploads the profile: re-installing with a node
token (opdnsnode_…) and enrolling from OPDNS_ENROL_TOKEN never do.
After enrolling, remove profile_file (and mode: standalone, if set)
from the config; the enrol command reminds you. A profile_file whose
profile was uploaded is no longer read: the node follows the dashboard.
One that was not uploaded (with --keep-cloud-profile, or when
re-installing with a node token) still wins over the dashboard’s profile
for as long as it is set.
Stop the node first (sudo systemctl stop opdns-node, or stop the
container), then:
Terminal window
sudo-uopdnsopdns-nodeunenrol
unenrolled node 7c9e…: revoked in the cloud; token, log cursor and learnt list keys removed
query logs, the last profile and lists are kept (--purge-logs deletes the logs)
It first revokes the node in the cloud with its own token, which
also closes its link session, as Revoke on the
Nodes page would. Then it removes the node token
(node.json), the log cursor and the signing keys learnt from the cloud
(lists_keys.json for lists, profile_keys.json for profiles). Logs, the last profile and the list artifact
stay on disk; --purge-logs deletes the logs too.
The cloud call gives up after a few seconds. If opdns cannot be reached,
unenrol goes on locally and prints a note asking you to revoke the node on
the Nodes page, so its token stops working. A node the dashboard already
revoked or deleted counts as done. --local-only skips the cloud call.
After unenrolling, the node does not serve DNS on its next start: like a
fresh install, it prints how to enrol and exits with status 2. Enrol it
again with a new code, or give it a profile for
standalone mode. It never falls back to
resolving unfiltered. If OPDNS_ENROL_TOKEN (enrol.token) is still set,
the next start enrols again with it; unenrol says so. Remove it to stay
unenrolled.
An enrolled node opens two kinds of outbound connection: HTTPS to
cloud.api_url (https://api.opdns.io: enrolment, profile and list
pulls) and the WebSocket link to cloud.link_url
(wss://link.opdns.net/link). Both use TLS 1.3 only, verified
against the system’s certificate authorities (plus cloud.ca_file), and
so do list artifact downloads.
cloud.proxy sends them through an HTTP proxy instead of HTTPS_PROXY;
its password is never printed or logged.
cloud.spki_pins additionally pins the keys of those two hosts: a list of
sha256/<base64> hashes of a certificate’s SubjectPublicKeyInfo, of which
at least one must appear somewhere in the verified chain. It is off by
default, because a TLS-inspecting proxy or a change of certificate
authority breaks a pinned node until you update the pins. List artifacts
are never pinned: their Ed25519 signature protects them wherever they come
from.
the code was used already, mistyped, or older than 15 minutes; create a new one
node stays offline in the dashboard
outbound HTTPS to api.opdns.io and link.opdns.net blocked, or a TLS-inspecting proxy in the way (connections to opdns); set cloud.proxy if you need a proxy
status says “revoked”
the node was revoked in the dashboard; it keeps resolving with its last profile. Enrol again or switch to standalone
exits with status 2 and “this node is not set up yet”
no identity and no enrolment code (also with mode: enrolled set): enrol it
exits with status 1 right after start with OPDNS_ENROL_TOKEN set
the code was refused (see invalid_token above) or the cloud was unreachable; the log says which
pass --yes to upload the local profile when enrolling from a script
the node has a local profile and no terminal to confirm the upload: add --yes, or --keep-cloud-profile to leave it out (Enrol a standalone node)
node_token_superseded
a request used the previous node token after a rotation (a copy of an old node.json, or a second install): use the current token, or rotate again from the running node
profile_not_empty when enrolling a standalone node