API keys
The firewall, the exposure of the management ports and Portainer are on The firewall and exposure; rate limits on Rate limits. Every key the server uses, with where it is kept, is listed in Keys and credentials.
Three keys, three jobs
| Key | Held by | What it is for |
|---|---|---|
| The console key | ohm-api and ohm-web | The key the console presents as X-Api-Key. Over the network it acts only for a person signed in to the console, so a remote ohm or a script uses an API key of its own |
| The agent's operator key | /etc/antelao/agent.env, and nowhere else | Full authority over the agent, including revealing and rotating the console key and reopening a locked-out server |
| The agent's consumer key | /etc/antelao/agent.env, presented by ohm-api | Lets the console dispatch operations to the agent. Scoped: the operator's own operations are refused with a 403 |
The two agent keys live on the host and not inside ohm-api, which is one of the things they
repair. ohm install writes them, keeps them across a repair, and replaces them only for
--rotate-secrets.
The console key is listed with the server's other credentials:
$ sudo ohm server info --show-secretsThe agent's two keys are a different pair, and only the server itself can read them. The operator key is what you sign in to the agent's page on port 5202 with:
$ sudo ohm server agentkey showAPI keys for other callers
Security > API keys issues keys (Issue a key), each under a name that identifies the caller in the job log. Never reuse one value under two names: the API logs a warning and attributes both callers to one name, which defeats the point.
The button beside it, Issue a key for a console that manages this server (the globe), issues the key another console enters for this server under Environments. With it, the people of that console work here only through an enabled account of their own on this server, with the same user name, and with no more than both that account and the key allow: the surfaces, the level and the instances you choose limit the key. The browser editor opened that way carries none of the account's git credentials or SSH keys. The list marks such a key (for a console that manages this server). An ordinary key is refused when a console uses it to act for a person.
What an issued key cannot do
An issued key is a program, not a person, and some reads stay with the people signed in to the console:
- It never reads an Odoo user's current two-factor code, whatever its level and whichever instances it covers. That code is for an Administrator in the console.
- A read-only key cannot download what carries credentials: configuration and manifest exports, Master data exports, database dumps and backup archives. It still lists them. A read-write key can download them, because moving an instance here, a restore or an upgrade from another server's instance and a workstation's pull rely on it.
- It cannot touch what belongs to a console account: its passkeys and trusted devices, its SSH keys and git credentials, its notification address, its password and authenticator, and its console sessions, which it can neither list nor end. Nor can it create, change, unlock or remove console accounts. Those belong to the account itself or to an Administrator in the console. It can still list the accounts.
- It acts under its own name only, reads the License page's status without the license key, and takes the one-time reveal of a generated password only from a job it queued itself.
A refused request answers 403 and is recorded in Server > Audit. The console's own key and a key from the server's configuration are not held to these rules. A key for a console that manages this server is held to them too, except that while it acts for a person, the role decides the rest: the lower of the one that console names and the one of the person's account here, within what the key allows.
The same goes for the command line: the ohm user commands that change an account, run against
another server with --server and an issued key, are refused. Run them on that server itself, or
change the account on its Users page. ohm user list still works remotely.
Revoking and deleting a key
Each row has at most one action:
| The key is | Action | What it does |
|---|---|---|
| Live | Revoke (the circle with a line through it) | Refuses the key from the next request on. The row stays in the list, marked revoked |
| Revoked, or past its expiry | Delete (the trash can) | Removes the row for good. The job log and Server > Audit keep naming the key by its description and prefix |
| The console's own key, or a key from the server's configuration | - | Neither: the console key is replaced by a rotation, and a configured key is the way back in after revoking the wrong one |
Both ask for confirmation in the row and are queued as jobs; only an Administrator can use them. A live key cannot be deleted - revoke it first. The API keys tab of Security > GeoIP lists the keys that reach GeoIP and offers the same two actions.
The list also shows a read-only GeoIP key named Access checks for instance ... for each instance whose firewall rules ask the server about a caller (a country, a host name, or pre-authentication). The server issues it to that instance's site and deletes it when the instance is removed; the daily API key expiry warning also deletes the ones left behind by instances that no longer exist. A key you issued yourself is never deleted by the server, even when the only instance it was limited to is gone.
From the command line
ohm apikey issues, lists and revokes the same keys. On the server, run as root, it uses the server's
own API. From elsewhere it needs a connection with an Administrator's key, and the API refuses
anything less. Issuing and revoking are queued as jobs and audited like the console's.
ohm apikey create --name "Nightly report" --surface api --read-only --expires 2027-01-31 --server pelmo
ohm apikey list --server pelmo
ohm apikey revoke "Nightly report" --server pelmo
Option of ohm apikey create | What it does |
|---|---|
-n, --name | What the key is for. Required |
--surface | A surface the key may reach, api or geoip. Repeat it for several. Left out, the key reaches api |
--read-only | The key may only read |
--instance | An instance the key is limited to, by its id. Repeat it for several. Left out, the key covers all of them |
--expires | When the key stops being accepted, such as 2027-01-31, read as UTC. Left out, it never expires |
The key is printed once, when it is issued, and cannot be read again. ohm apikey revoke takes the
key's id, prefix or exact name as ohm apikey list shows them, asks for confirmation unless you
pass --yes, and refuses a name that matches more than one key. Deleting a revoked key is done in
the console.
ohm apikey create and ohm apikey list take --json for scripts. Add -v None so that only the
JSON reaches standard output:
ohm apikey list --json -v None --server pelmo
Rotating the console key
pelmo$ ohm server apikey rotateThe API mints a new key, hands it to the agent, and the agent recreates both management containers with it. The key being replaced keeps working for ten minutes, which is what stops the rotation cutting off the connection it travelled over. Every remote client using the old key must be updated inside that window.
--yes skips the confirmation. ohm install does the same as part of a repair.
The agent's own keys rotate separately, and that one is local-only - it is a repair command that has to keep working when the API is unreachable:
$ sudo ohm server agentkey rotateSymptoms of a key mismatch
| Symptom | Cause |
|---|---|
| Every API call returns 401 | The console's key is not the one the API adopted. Rotate it from Security > API keys, or run ohm install |
| Every API call returns 503 | The API could not compose its configuration. Check GET /health for serverConfigError, then whether the agent is running |
| The console reports the API as unreachable | ohm-api is not running or not listening on 5311 |
See Troubleshooting for the diagnostic commands.
Portainer
Installing and removing Portainer, and why to keep it closed, are on The firewall and exposure.