Deployment Tool
What it does
The deployment tool, datumhue-deploy, turns one description of a
deployment, its hosts, its aggregator and its providers with their
mounts, sign-in services, channels and rules, into a bundle with one
folder per host. Each folder holds that host's settings, identity keys,
capability files, certificates, service units and install steps; the
aggregator's also holds the deployment secret. The tool also makes
capabilities for a provider added later, and revokes a provider's key.
See the Overview for the whole deployment.
Quick start
chmod +x datumhue-deploy-linux-x86_64
./datumhue-deploy-linux-x86_64 init deployment.kdl
$EDITOR deployment.kdl
./datumhue-deploy-linux-x86_64 validate deployment.kdl
DATUMHUE_KEYSTORE_PASSPHRASE='choose-a-strong-passphrase' \
./datumhue-deploy-linux-x86_64 generate deployment.kdl -o bundle/ \
--license customer.dhlicense
Then follow bundle/CHECKLIST.md and the README.md in each host's
folder.
How it works
initwrites a starter description, with a comment on every key.- You edit it.
validatechecks it, and reports what is wrong or doubtful.generate --license customer.dhlicensewrites the bundle, with a new deployment secret and every provider's capabilities signed under it.- You follow
bundle/CHECKLIST.md, then theREADME.mdin each host's folder, on that host.
The tool owns everything that ties the services together: URLs, keys, capability files, environment files and units. The blocks that say what each provider serves, its mounts, sign-in services, channels and publish rules, go into that provider's settings as you wrote them, in the form its own page in this manual describes.
Without a license file, generate writes license-request.kdl,
filled in with the deployment's counts, for a license DatumHue issues
by hand. With one, it checks the description against the license's
counts.
The deployment secret signs every capability file. generate
makes a new one, or keeps the one --deployment-secret names, and
writes it to the aggregator host's state/.
A new or renewed license leaves the capability files in force.
add-provider and reissue-capabilities read the secret from the
bundle, or from --deployment-secret once you keep it elsewhere, and
stop without it; reissue-capabilities --new-deployment-secret replaces
it.
The access key admits the deployment's workstations. generate
makes one and writes it to the aggregator host's etc/access.env, to
datumhue-client.env for the workstation app and command line, and as
the browser access link to access-link.txt; each file is readable by
its owner only. Whoever holds the key gets a workstation seat, so give
it only to the deployment's people, and make a new one with
new-access-key when someone leaves.
The admin socket, through which aggregator lease lists the seats
and ends workstation sessions, is set up in the aggregator's unit and
compose stack; the aggregator host's README.md gives the command.
The bundle holds every key. The keys and the deployment secret are
encrypted with DATUMHUE_KEYSTORE_PASSPHRASE; every service, the
aggregator included, reads it from its host's keystore.env.
Capabilities last 30 days. A running provider renews its own before
they end. A provider stopped for longer needs its file made again with
reissue-capabilities.
Revoking a provider key
A revocation withdraws a provider key's right to its namespaces before
its capabilities end: aggregators then refuse the key's announcements,
renewals, seat and sign-in key publishing, and leave its provider and
the keys it published out of every lookup. revoke signs one
revocation for each namespace the description grants the provider, and
for each --namespace and --issuer-url you add; lift signs the
lifts that restore them. For each key and namespace, the newest record
stands.
Each record is added to the bundle's revocations.txt. --aggregator
submits the records at once, and any aggregator passes them to the
others. --out writes them to a file instead, for submit on a host
that reaches an aggregator:
datumhue-deploy revoke deployment.kdl --provider identity-provider \
--host providers-1 --key <public key> --out revoke.txt
datumhue-deploy submit revoke.txt --aggregator https://datumhue.example
The command submits every record, and ends with an error unless the aggregator accepted each one. Then replace the key:
-
Make the provider a new key in the bundle, with its capability file:
shelldatumhue-deploy add-provider deployment.kdl \ --provider identity-provider --host providers-1 --out bundle -
Install its files on the host as its
README.mddescribes, and restart the provider. -
Replace each secret the host held, such as a sign-in service's
client-secret.
reissue-capabilities makes no capability file for a key that stands
revoked in revocations.txt. With --new-deployment-secret, it also
writes the aggregator host's state/aggregator/revocations: install
that file with the secret, so the revoked keys stay revoked.
Command line
datumhue-deploy <COMMAND>
| Command | Description |
|---|---|
datumhue-deploy init | Write a commented starter deployment description. |
datumhue-deploy validate | Check a deployment description and report findings; exits non-zero on errors. |
datumhue-deploy generate | Generate the per-host installation bundle. |
datumhue-deploy reissue-capabilities | Make every provider's capability file of a bundle again, keeping the bundle's keys: for capabilities that lapsed while a provider was stopped, or under a new deployment secret. |
datumhue-deploy new-access-key | Replace the deployment's access key in a bundle: the aggregator host's etc/access.env, datumhue-client.env and the browser access link. Install the new etc/access.env on every aggregator and restart them together; sessions on the old key end within seconds. |
datumhue-deploy add-provider | Make the files of one provider added to a deployment: its key, its capability file, its settings and its service unit, without generating the bundle again. |
datumhue-deploy revoke | Revoke a provider key before its capabilities end: aggregators refuse it, and leave it and the sign-in keys it published out of every lookup. |
datumhue-deploy lift | Lift a provider key's revocation, restoring what it withdrew. |
datumhue-deploy submit | Submit the records of a file revoke --out or lift --out wrote, or of the bundle's revocations.txt, to an aggregator. |
datumhue-deploy init
datumhue-deploy init [OPTIONS] [PATH]
| Argument | Default | Description |
|---|---|---|
[PATH] | deployment.kdl | Where to write the description |
| Flag | Default | Description |
|---|---|---|
--force | off | Overwrite an existing file |
datumhue-deploy validate
datumhue-deploy validate <PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description to check |
datumhue-deploy generate
datumhue-deploy generate [OPTIONS] <PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description to generate from |
| Flag | Default | Description |
|---|---|---|
--out <OUT>, -o | bundle | The folder to write the bundle into |
--force | off | Overwrite a non-empty output directory |
--license <LICENSE> | none | The deployment's issued .dhlicense, checked against the description's counts. Without it, generate writes license-request.kdl, for a license DatumHue issues by hand |
--deployment-secret <DEPLOYMENT_SECRET> | none | A deployment secret to keep, such as the one your aggregators already run under. Without it, generate makes a new one |
datumhue-deploy add-provider
datumhue-deploy add-provider [OPTIONS] --provider <PROVIDER> --host <HOST> \
<PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description, with the new provider's block added |
| Flag | Default | Description |
|---|---|---|
--license <LICENSE> | none | The deployment's issued .dhlicense, checked against the description's counts |
--bundle <BUNDLE> | bundle | The bundle directory generate wrote, which keeps the deployment secret the capability file is signed under |
--deployment-secret <DEPLOYMENT_SECRET> | none | The deployment secret, when it is no longer in the bundle |
--provider <PROVIDER> | required | The kind of provider to add, as its block in the description names it, such as fs-data-provider |
--host <HOST> | required | The host the provider runs on, by its name in the description |
--out <OUT>, -o | bundle-add | The folder to write the new provider's files into |
add-provider writes the new provider's settings, key, capability file
and unit into a folder for its host, in the bundle's layout. Install
them as that host's README.md describes.
datumhue-deploy reissue-capabilities
datumhue-deploy reissue-capabilities [OPTIONS] <PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description the bundle was generated from |
| Flag | Default | Description |
|---|---|---|
--license <LICENSE> | none | The deployment's issued .dhlicense, checked against the description's counts |
--bundle <BUNDLE> | bundle | The bundle directory generate wrote |
--deployment-secret <DEPLOYMENT_SECRET> | none | The deployment secret, when it is no longer in the bundle |
--new-deployment-secret | off | Make a new deployment secret and every capability file under it; install the secret on every aggregator and the files on every provider host |
After a reissue, install each host's new
state/<service>/<service>.; running providers take it
without a restart. After --new-deployment-secret, first install the
bundle's new state/ on every aggregator,
with state/aggregator/revocations when the bundle has one, and restart
them together. Each package provider then stops once when its new file
arrives, and keeps its catalog when its service starts it again.
datumhue-deploy new-access-key
datumhue-deploy new-access-key [OPTIONS] <PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description the bundle was generated from |
| Flag | Default | Description |
|---|---|---|
--bundle <BUNDLE> | bundle | The bundle directory generate wrote |
It replaces the key in the aggregator host's etc/access.env,
datumhue-client.env and access-link.txt, and prints the new key's
id. Install the new etc/access.env on every aggregator and restart
them together; each logs the id at start. Sessions that took their seat
with the old key end within seconds. Then give the deployment's people
the new datumhue-client.env, and browser users the new access link.
datumhue-deploy revoke
datumhue-deploy revoke [OPTIONS] --provider <PROVIDER> --host <HOST> \
--key <KEY> <--aggregator <AGGREGATOR>|--out <OUT>> <PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description |
| Flag | Default | Description |
|---|---|---|
--bundle <BUNDLE> | bundle | The bundle directory generate wrote, which keeps the deployment secret and the ledger of every revocation signed for the deployment, revocations.txt |
--deployment-secret <DEPLOYMENT_SECRET> | none | The deployment secret, when it is no longer in the bundle |
--provider <PROVIDER> | required | The provider, as its block in the description names it, such as identity-provider |
--host <HOST> | required | The host it runs on, by its name in the description |
--key <KEY> | required | The provider's public key, as it logs it at start and pubkeys.txt lists it |
--namespace <NAMESPACE> | none | A namespace to cover besides those the description grants the provider; repeat for several |
--issuer-url <ISSUER_URL> | none | An issuer URL whose sign-in keys to cover besides those the description gives the provider; repeat for several |
--aggregator <AGGREGATOR> | none | Submit each record to this aggregator now, by its public URL |
--out <OUT> | none | Write the records to this new file instead, for submit on another host |
datumhue-deploy lift
datumhue-deploy lift [OPTIONS] --provider <PROVIDER> --host <HOST> --key <KEY> \
<--aggregator <AGGREGATOR>|--out <OUT>> <PATH>
| Argument | Default | Description |
|---|---|---|
<PATH> | required | The deployment description |
| Flag | Default | Description |
|---|---|---|
--bundle <BUNDLE> | bundle | The bundle directory generate wrote, which keeps the deployment secret and the ledger of every revocation signed for the deployment, revocations.txt |
--deployment-secret <DEPLOYMENT_SECRET> | none | The deployment secret, when it is no longer in the bundle |
--provider <PROVIDER> | required | The provider, as its block in the description names it, such as identity-provider |
--host <HOST> | required | The host it runs on, by its name in the description |
--key <KEY> | required | The provider's public key, as it logs it at start and pubkeys.txt lists it |
--namespace <NAMESPACE> | none | A namespace to cover besides those the description grants the provider; repeat for several |
--issuer-url <ISSUER_URL> | none | An issuer URL whose sign-in keys to cover besides those the description gives the provider; repeat for several |
--aggregator <AGGREGATOR> | none | Submit each record to this aggregator now, by its public URL |
--out <OUT> | none | Write the records to this new file instead, for submit on another host |
datumhue-deploy submit
datumhue-deploy submit --aggregator <AGGREGATOR> <FILE>
| Argument | Default | Description |
|---|---|---|
<FILE> | required | The file revoke --out or lift --out wrote, or the bundle's revocations.txt |
| Flag | Default | Description |
|---|---|---|
--aggregator <AGGREGATOR> | required | The aggregator to submit to, by its public URL |
submit submits the file's records in order. Given the bundle's
revocations.txt, it gives a new aggregator every record; an
aggregator that already holds them answers Stale, so submit ends
with an error after submitting every line.
Environment
| Variable | Description |
|---|---|
DATUMHUE_KEYSTORE_PASSPHRASE | generate, add-provider and reissue-capabilities encrypt the keys and the deployment secret with it, or read them with it, and stop without it. revoke and lift read the bundle's deployment secret with it. The services decrypt them with the same value, from each host's keystore.env |
DATUMHUE_CERT_PATH | A CA certificate revoke, lift and submit trust, besides the public ones, when they reach an aggregator, such as the bundle's ca-cert.pem |
Configuration
The description is KDL: one deployment block that names the
deployment and holds the customer id, the hosts, one aggregator block
and a block for each provider. A key the tool does not know, in the
deployment block, the aggregator block or a provider's own
settings, is an error that validate reports.
deployment "acme-prod" {
customer-id "acme"
host "agg-1"
host "providers-1"
aggregator {
host "agg-1"
public-url "https://datumhue.acme.example"
tls "self-managed"
max-sessions 100
}
identity-provider {
host "providers-1"
issuer "keycloak" {
issuer-url "https://keycloak.acme.example/realms/acme"
client-id "datumhue-client"
scopes "profile" "email" "groups"
}
}
fs-provider {
host "providers-1"
fs-mount "shared" {
path "/mnt/nas/shared"
mode "rw"
issuer "https://keycloak.acme.example/realms/acme" {
allow claim="groups" array_contains="acme-users" \
rights="rw"
}
}
}
}
Deployment
| Key | Required | Description |
|---|---|---|
deployment "<name>" | yes | The deployment's label, which names the bundle and appears in the files it holds |
customer-id | yes | Your customer id, which the license request carries |
host | yes | A host's label, once for each host. Each becomes a folder of the bundle, and every service names one |
Aggregator
| Key | Required | Default | Description |
|---|---|---|---|
host | yes | none | The host that runs the aggregator |
public-url | yes | none | The HTTPS URL workstations and providers use, also the relay and discovery URL |
https-bind | no | 0.0.0.0:443 | The HTTPS address |
quic-bind | no | 0.0.0.0:7842 | The UDP address of public-address lookups for the workstation app and providers |
stun-bind | no | 0.0.0.0:3478 | The UDP address of public-address lookups for browsers |
webrtc-bind | no | 0.0.0.0:7843 | The UDP address of browsers' direct connections |
webrtc-public | no | the public URL's host and the webrtc-bind port | The host:port browsers reach it at, when that differs |
static-dir | no | /opt/datumhue/static | The folder of the browser workstation |
tls | no | "self-managed" | Where the certificate comes from, as Certificates describes |
max-sessions | no | 100 | The session count the license request asks for |
Providers
A block for each provider instance: package-provider, fs-provider,
fs-data-provider, adbc-data-provider, identity-provider,
egress-provider or ingress-provider. A host runs at most one of each
kind, and a deployment at most one egress-provider.
| Key | Applies to | Required | Description |
|---|---|---|---|
host | every provider | yes | The host that runs it |
namespace | package-provider | yes | The namespace it serves |
listen | ingress-provider | no | The address it takes events on |
outbound-proxy | egress-provider | no | The proxy its requests to the origins go through: an http:// or https:// URL with no user name or password |
Everything else in a provider's block says what it serves: fs-mount,
fs-data-mount, adbc-data-mount, issuer, http-mount,
ingress-channel and publish-rules, in the form the provider's own
page describes. The tool makes a capability for each one, in that
provider's capability file. An issuer gets two: one to announce it
by name, and one to publish its keys, made for its issuer-url and
named by it in a comment above the line.
More than one copy. For availability, give a provider block of the same kind, with the same mounts, to more hosts. Each copy serves the same mounts with its own key and capabilities, and workstations reach whichever copies run. Each copy takes one of the license's provider seats.
Certificates
tls "self-managed"makes a certificate authority and a server certificate for the public URL's host, in the aggregator host'stls/folder, and copies the CA certificate to every host'setc/and the bundle's top folder. Providers and workstations then trust the aggregator from their first start, and an aggregator restart renews the server certificate once it has less than 30 days left.tls "operator"takes a certificate and key from your own certificate authority; the settings name where you install them, and the tool makes no certificates.
Access rules
The tool copies each mount's, channel's and registry's rules into its
provider's settings, and validate loads them as the provider will.
Every mount and channel needs a rule; one without is an error, and so
is a rule its provider would refuse. An issuer block must name an
issuer-url of the identity-provider exactly as written. A rule that
reads a sign-in outside an issuer block is an error, and when the
description declares one sign-in service, the error shows the block to
write. allow anyone needs no sign-in service and is a warning, in the
words its provider logs at start.
These are warnings too:
- a rule on a claim no sign-in service asks a scope for;
- a rule on
emailorphone_number, which matches only a token that says the sign-in service verified the value; allow signed-inunderhttps://accounts.google.comwithout arequire claim="hd", which admits every Google account;allow signed-ininpublish-rules, which lets everyone the issuer signs in publish.
The rules themselves work as each provider's page describes.
Network, state and dependencies
Network surface
Only submit, and revoke and lift with --aggregator, connect
anywhere: to that aggregator's public URL, over HTTPS. Everything else
runs on any Linux machine, offline included, and reads only the
description, the license file, the bundle and the deployment secret.
Persistent state
The tool keeps nothing of its own, but its inputs and outputs are the deployment's records:
| Path | Holds |
|---|---|
deployment.kdl | The description, which every later add-provider and reissue-capabilities reads |
bundle/ | Every service's settings and keys, the CA's private key, and the deployment secret in the aggregator host's state/aggregator/ |
bundle/revocations.txt | Every revocation and lift the tool signed, which reissue-capabilities reads. Keep it with the bundle |
bundle/datumhue-client.env, bundle/access-link.txt | The workstations' settings with the access key, and the browser access link |
| The passphrase | The only way to read the keys again |
External dependencies
- The license file,
customer.dhlicense, which--licensechecks the description against.
Security notes
- The bundle holds private keys, the CA's among them. Move it over a secure channel, and delete working copies once the hosts are set up.
- The passphrase protects every key. Keep it in your secret store;
each host needs it in
keystore.env, mode 600. - The deployment secret makes capabilities. Anyone with it and this tool can authorize a provider: keep it in your secret store, as safe as the keys, and never copy it to another deployment.
- The access key admits workstations. It admits whoever holds it,
as one of the deployment's people: hand
datumhue-client.envand the access link out privately, and replace the key withnew-access-keywhen someone should no longer get in.
Troubleshooting
validate reports errors. Each one stops generate:
- a host that is not declared, or declared twice;
- no
aggregatorblock, or apublic-urlthat is not HTTPS; - a bind address that does not parse;
- a mount or channel name used twice on one host, or by two kinds of provider. The same name on more hosts of one kind is a set of copies, and allowed;
- a
package-providerwithoutnamespace; - an
issuerwithoutissuer-url, or with one that is not anhttpsURL with no query, fragment or user name; - two
issuerentries of one provider with the sameissuer-url; - an
issuerwhosejwks-ttlis longer than"24h", which aggregators keep published keys for at most; - an
issuerwhoseperson-claimmany people share or a person can change, such astidoremail; - a mount or channel without rules, a rule its provider would refuse,
or a rule that reads a sign-in outside an
issuerblock; - an
issuerblock whose URL is not anissuer-urltheidentity-providerdeclares; - two providers of one kind on one host;
- two keys on one line, such as
{ path "/x" mode "r" }: KDL reads that as onepathwith extra values. Put each key on its own line, or end each with;.
validate reports warnings. Generation goes on, and the warnings
appear in CHECKLIST.md: for example, a rule on a claim that no
sign-in service asks a scope for, an allow anyone, or
person-claim "oid" without the profile scope, which Microsoft Entra
ID puts oid in ID tokens for.
generate refuses the license. The file is not an Enterprise
deployment license, or the description has more providers than the
license's provider count. A description with more max-sessions than
the license only warns: the license's count applies.
reissue-capabilities cannot read a key. The passphrase differs
from the one generate used: set DATUMHUE_KEYSTORE_PASSPHRASE to that
one.
reissue-capabilities or add-provider stops: no deployment
secret. The bundle holds no state/.
Pass --deployment-secret with the file your aggregators run under;
reissue-capabilities --new-deployment-secret makes a new one instead,
which every aggregator then needs.
A service cannot read its key. The passphrase in the host's
keystore.env differs from the one the bundle was made with.
revoke, lift or submit reports a record not accepted. Each
line names what the aggregator decided. Rejected: it runs under
another deployment secret than the one the record is signed under.
Stale: it holds a record for that key and namespace at least as new,
which stands; run revoke or lift again with --aggregator to sign
past it.
revoke or lift stops: the aggregator holds records under another
deployment secret. The aggregator --aggregator names runs under
another secret than the one the command read, or belongs to another
deployment. Pass --deployment-secret with the file your aggregators
run under, or name one of this deployment's aggregators. Nothing is
signed or recorded.
reissue-capabilities warns that a key is revoked. That provider
keeps the file it has. add-provider --out bundle makes it a new key,
as Revoking a provider key
describes; lift restores the old key instead.