Signing in¶
A person signs in once with port49 login, then asks for a token for each device with port49 token
or lets port49 ssh do both steps. This page is for the operator who has to explain what those
commands do, and what they return to a script. The pieces they talk to are on
Broker and agent.
port49 login¶
port49 login proves who the person is to the broker and starts the agent that keeps the result. It
takes one of three methods, each the same command with a different flag.
Instead of a flag, the person can set the method once in their config file with method:. A flag
always overrides its config key. The keys are in the
client configuration reference.
# A client certificate and key the person already holds
port49 login --broker https://broker.example.com:8443 --broker-ca <broker-ca.pem> \
--cert <client-cert.pem> --key <client-key.pem>
# A sign-in at the identity provider; the broker issues the certificate
port49 login --oidc
# A directory name, password and authenticator code, asked at the terminal
port49 login --directory
A successful login prints port49: logged in to <broker>; the session ends at <time>, with the
agent's process id and the path of its log.
A second port49 login while the agent runs refreshes that agent instead of starting another one. It
says already logged in to <broker>; refreshing.
--oidcand--directoryhold the certificate the broker issues. They sign in again only when that certificate has under a quarter of its life left, or when you pass--sign-in.--certand--keybelong to the certificate method. Combining them with--oidcor--directoryis refused, as is naming both of those two together.- The key may be on a hardware token. A
keythat startspkcs11:is asked for its PIN once, on the terminal. See Hardware keys. - Where the broker and its CA come from. Flags first, then the person's own config file, then
/etc/port49/config.yamlkey by key. The broker's CA is required; the system's roots are never trusted for the broker.
port49 token¶
port49 token core-sw01 # prints the login token, and nothing else, on stdout
port49 token --ttl 30s core-sw01 # shorten the token's life below your token_ttl; never lengthens it
port49 token --json core-sw01 # the broker's whole answer as one JSON object
A token's life is set by policy, not by you. Each subject's token_ttl (30s to 900s, on the
policy reference) is how long its token stays usable before its one
login. --ttl can only ask for less: a value above token_ttl gets token_ttl. How long the session on
the device then lasts is the role's max_session_age, a
separate setting (The token).
Flags come before the device name.
The token is the one secret that port49 ever writes to standard output. Give it to the device where the device asks for a password. It is single use and short-lived by design: see Tokens, receipts and contexts.
A store that is not ready is retried; nothing else is. If the broker's store is not ready,
port49 token tries again by itself for up to 30 seconds, waiting the broker's own hint between
tries. After that it fails with the last answer.
A second factor comes first. When a device's policy requires one, port49 token asks for it
before the mint: a touch on the person's security key, or a sign-in at the identity provider. It
prints which one, and what to do.
--json¶
With --json, standard output is exactly one JSON object whether the mint worked or not, so a
script reads one thing. Standard error carries the human words.
On success it is the broker's answer for that device. These are its keys:
| Key | Meaning |
|---|---|
device |
the device as the inventory names it |
verdict |
permit |
address, platform, username |
where to connect and as whom |
login |
the login token: <token> |
enable |
the enable token, present only with --enable |
expires_at |
when the token stops being redeemable |
session_expires_at |
when a login made with it stops being authorized; absent when the roles say never |
The object holds the tokens, so treat it as a secret: do not log it.
The --enable flag mints an enable token beside the login token. Without --json, only the login
token is printed.
On a failure the command exits 1 and the object has device and one of these shapes:
- A refusal by policy.
verdictisdeny, anddetailsays why in words.reasonis a code from Reason codes, the same closed vocabulary the decision log uses. It is never a code that table does not list. - A failure of the client or the broker.
erroris one of the codes below, anddetailsays more. These codes belong to the command and are never reason codes. The one exception is a device the broker could not decide: thenerroris the broker's code for it. - A requirement to prove.
errorisper_login_mfa_requiredandper_login_mfalistsaccept(the methods that satisfy it),reuse_for_seconds, and where the reuse comes from. A challenge is never printed.
error |
What it means, and what to do |
|---|---|
no_agent |
nothing listens at the agent's socket: run port49 login, or check XDG_RUNTIME_DIR |
agent_too_old |
the agent was started by an older port49: run port49 login again |
session_over |
the session has ended: run port49 login |
credential_revoked |
the broker refuses this credential or session; a revocation is in force |
revocation_running |
a revocation of yours is still running on the broker; run the command again when it reports |
per_login_mfa_required |
prove the second factor, then run it again |
connection_changed |
the connection changed during a touch; nothing was sent: run it again |
broker_refused |
the broker refused the request or a proof, or its certificate was refused by the CRL check |
broker_unavailable |
the broker did not answer; try again when it does |
key_locked |
the hardware key needs its PIN: run port49 login (Hardware keys) |
key_absent |
the hardware key is not inserted |
touch_timeout |
no touch arrived in time; run it again and touch when the key blinks |
no_token |
the broker said permit and gave no token; tell the broker's operator |
agent_error |
any other fault of the agent or the broker; the detail has the words |
A token near the end of the session's ceiling also prints a warning. The warning goes to standard
error, never to standard output, so --json stays one object.
port49 ssh¶
port49 ssh does the mint and the connection together. It asks the agent for a token, runs your own
OpenSSH (8.5 or later) with the token ready for the device's password prompt, and exits with ssh's
status.
port49 ssh core-sw01 # a shell
port49 ssh core-sw01 -- show version # one command
port49 ssh --port 2222 --ttl 30s core-sw01 # --ttl shortens the token's life; never lengthens it
Its flags:
--ttl, shorter than yourtoken_ttlonly, as forport49 token--port--ssh PATH, for another OpenSSH--ssh-config FILE, for anotherssh_config-v
The device name may hold only letters, digits, ., _ and -. For any other name, use
port49 token and your own ssh.
A login costs one attempt. The options port49 ssh passes to ssh are fixed: no public-key
offers, one password prompt, no connection sharing, no agent forwarding.
The token goes only to the device it was minted for. A prompt from anything else on the way, such
as a jump host that asks for its own password, is not answered with it. If the device asks for the
password a second time, port49 ssh ends the connection, so no second or empty password is sent.
Host keys¶
OpenSSH owns the device's host key. port49 does not pin one, keep a list, or add a trust step of its own.
port49 ssh runs ssh with StrictHostKeyChecking=accept-new:
- A device met for the first time is accepted and filed in your
known_hosts. - A device whose key has changed is refused.
- If your own
ssh_configsaysStrictHostKeyChecking yes, that stands, and the key must already be inknown_hosts.
The keys are filed under the device's name, lowercased, unless your ssh_config sets a
HostKeyAlias.
Two consequences an operator should know:
- If ssh says
Host key verification failed, the key filed under the device's name is not the one it presented. Verify the new key on the device's console, then remove the old one withssh-keygen -R <device>(the failure message prints the exact command for yourknown_hosts) and connect again. Do not remove it first and accept whatever answers. - Under
accept-new, if yourssh_configpointsUserKnownHostsFilesomewhere that cannot keep a key, such as/dev/null,port49 sshrefuses before sending anything, because every connection would be a first contact. With your ownStrictHostKeyChecking yesthat refusal does not apply.
Telling port49's refusals from ssh's¶
A remote command can exit 1, and so can port49 ssh. Look for a line that starts port49 ssh: on
standard error to tell them apart.
- port49 refused. It says so under the
port49 ssh:prefix, and it sent nothing to the device. - ssh or the device failed. ssh's own messages have no such prefix. When ssh exits 255 after the
device was sent the token,
port49 ssh:adds a line saying so, withport49 explain --device <device>as the next step.
Exit codes¶
These codes are taken from the commands' code.
| Command | Code | Meaning |
|---|---|---|
port49 login |
0 | logged in, or an agent already running was refreshed |
port49 login |
1 | the agent did not start, or did not answer within 15 seconds (the end of agent.log is printed); another broker's or another certificate's agent holds the socket, or --sign-in was given while one runs; refreshing the running agent failed; the agent socket's directory is not private; the sign-in failed at the identity provider or the directory; the broker did not answer; the broker refused the certificate it issued; a hardware key is absent, blocked or refused its PIN |
port49 login |
2 | the command line or the config is refused: an unknown flag, flags that contradict each other, a config file that cannot be read or that others can write, a missing --broker or --broker-ca, or a key file that is readable by others |
port49 ssh |
0 | ssh ended with 0 |
port49 ssh |
1 | port49 refused before ssh ran: no OpenSSH, one older than 8.5, a mint that failed, no agent, a socket directory that is not private, an ssh_config that keeps no host keys under accept-new, or a config file refused while proving an idp_mfa device's sign-in. Also ssh's own status when the remote command exits 1. Read the port49 ssh: line |
port49 ssh |
2 | a flag it does not accept, no device named, or a device name it does not accept |
port49 ssh |
255 | ssh's own failure (connection, host key, authentication), or port49 ended ssh: the asker could not be verified, the token was asked for after it expired, or the device asked for the password again |
port49 revoke |
0 | done; --list printed |
port49 revoke |
1 | refused or unreachable: no agent, the right missing, the touch refused or not given, a declined confirmation (nothing was revoked), an unreadable --cert file. Also when the broker did not answer, or answered without naming a revocation, and the revocation may be in force (it may be in force, it may have run): run port49 revoke --list before you retry |
port49 revoke |
2 | the flags are wrong: no target, two targets, or a flag that does not go with the form |
port49 revoke |
3 | the revocation is in force but incomplete or not saved, or a lift step did not finish: run the same command again |
port49 hwkey |
0 | ready; for setup, the keys are written, or already name this token |
port49 hwkey |
1 | action needed: a check failed, no token is inserted, the session key is not on a token, or setup would change a key and was not allowed to |
port49 hwkey |
2 | an error: a bad flag, no attestation roots, an unreachable or untrusted broker, a config file that is refused or cannot be written, no module found or one refused, several tokens with no --token, or the build has no hardware-key support |
port49 token and port49 refresh exit 0 when they worked, 1 when they did not, and 2 for a usage
error. Under --json, port49 token writes its failure object before it exits 1.
One refused config file gives different codes. A config file that others can write is refused, but not at the same moment by every command:
port49 loginreads it first and exits 2.port49 sshandport49 tokenread it only when a device asks for anidp_mfasign-in. There the refusal ends the mint with exit 1.
A script that reads the cause from the exit code alone will get this case wrong.
A script should check the code, not the text. A command run by cron or a script reads the exit
code. The words are meant for a person and may change.