Skip to content

port49d configuration

port49d reads port49d.yaml once, at start. port49-broker.yaml is the same schema, read by port49-broker.

A change takes effect at the next restart, with these exceptions, which an entry marks:

  • the policy file and the inventory file, re-read every policy_watch_interval and at SIGHUP
  • inventory.max_removed_percent and inventory.service.verify_key_files, at SIGHUP
  • the tls_listener certificate, key and CA files, every reload_interval and at SIGHUP

The file that a key version's secret_ref names is read once, when the version comes into force: at start, or in a policy reload that adds it. Later reads, at SIGHUP and every 30s, only report a file that has changed or can no longer be read. port49d keeps the secret it read, so a changed secret goes in a new file under a new version.

An unknown key, a second YAML document and a key the loader has removed refuse the start. The error names the line, never the value.

Durations are written with a unit (30s, 15m, 12h). A bare number is refused, except where an entry says 0 is allowed.

Paths are read as written: a relative path resolves against port49d's working directory, not the file's directory.

Top-level keys

Key Type Description
listen string Address of the TACACS+ listener (RFC 8907), host:port. Default :49. The value none turns the listener off, which needs tls_listener: a file with neither listener is refused. Needs a restart.
metrics_listen string Address of the metrics, /healthz and /readyz listener. Default :9049. Needs a restart.
inventory_file string Path of the device inventory. Exactly one of inventory_file and inventory.service is required. Without a policy the file is read once, at start; with a policy it is re-read at every policy watch and at SIGHUP, and a file that does not load leaves the previous inventory serving. A relative path resolves against port49d's working directory.
receipt_idle_ttl duration How long a receipt stays good after its last presentation: each presentation it answers slides the window forward. Default 15m; must be greater than zero and not longer than receipt_max_age. A presentation past either bound is a FAIL on the wire and token_spent in the log. Needs a restart.
receipt_max_age duration The longest a receipt lives from its first redemption, however often it is presented. Default 2h; must be greater than zero. Needs a restart.
context_idle_ttl duration How long a context with no activity is kept. Default 12h; must be greater than zero. Needs a restart.

tls_listener

Key Type Description
tls_listener.listen string Address of the TACACS+ over TLS 1.3 listener (RFC 9887). Default :300. It may not bind the same address as listen. Needs a restart.
tls_listener.cert_file string Server certificate chain, PEM, leaf first. Required, and it must be readable. Re-read every reload_interval and at SIGHUP.
tls_listener.key_file string Private key of cert_file. Required. Re-read every reload_interval and at SIGHUP; the key must be owned by port49's user or root, with no group or other mode bit.
tls_listener.client_ca_file string The device CA certificates, PEM: the anchors a device certificate must chain to. Required. Re-read every reload_interval and at SIGHUP.
tls_listener.intermediates_file string Optional PEM file of issuing CAs held here, so a device that sends only its leaf certificate still verifies. Re-read every reload_interval and at SIGHUP.
tls_listener.device_identity.match list of string Which certificate names identify a device: dns_name, ip_address, or both, each at most once. Default both. An empty list is refused. Needs a restart.
tls_listener.device_identity.dns_suffixes list of string DNS suffixes a dns_name identity may carry, lower-cased with leading and trailing dots trimmed; letters, digits, - and ., no wildcard. Needs dns_name in match. Needs a restart.
tls_listener.server_names list of string When set, the SNI names a ClientHello may ask for: DNS names (lower-cased) and IP addresses (canonical form). A name that looks like an address but is not one in canonical form is refused. Needs a restart.
tls_listener.handshake_timeout duration How long a TLS handshake may take. Default 10s; 1s to 30s. Needs a restart.
tls_listener.session_ticket_lifetime duration Lifetime of the TLS session tickets the listener issues. Default 0s, which issues none; at most 1h. Needs a restart.
tls_listener.reload_interval duration How often the certificate, key, CA and intermediates files are re-read. Default 1m; 10s to 1h. SIGHUP re-reads them at once. A change to this key needs a restart.
tls_listener.state_dir string Absolute directory where the listener keeps the certificate revocation lists it verified, in its tls_listener subdirectory, so a restart keeps them. Required when certificate_status.crls names any. Needs a restart.
tls_listener.certificate_status.disabled boolean Set true to check no revocation status. Then none of crls, ocsp, refresh_interval and on_unavailable may be set. The block is required: leaving it out is refused, so skipping the check is always a written choice. Needs a restart.
tls_listener.certificate_status.crls.[].name string Name of this revocation list source: lowercase letters, digits and _, starting with a letter, at most 64, and unique in the block. It names the kept file crl-<name>.der and the crl_next_update metric label.
tls_listener.certificate_status.crls.[].file string Path of a CRL file. Set exactly one of file and url.
tls_listener.certificate_status.crls.[].url string An http or https URL to fetch the CRL from. Set exactly one of file and url.
tls_listener.certificate_status.crls.[].issuer_file string Required. The CA certificate that signs the list: the trust anchor, or a certificate the anchor signed.
tls_listener.certificate_status.ocsp.responder string An http or https URL of a fixed OCSP responder. Set this or use_aia: true.
tls_listener.certificate_status.ocsp.use_aia boolean Set true to ask each certificate's own OCSP responder, from its Authority Information Access extension. Set this or responder.
tls_listener.certificate_status.ocsp.timeout duration How long one OCSP request may take. Default 2s; 100ms to 10s.
tls_listener.certificate_status.ocsp.hash string The hash of the OCSP CertID: sha256 (default) or sha1.
tls_listener.certificate_status.refresh_interval duration How often the revocation lists are fetched or re-read. Default 10m; 1m to 24h.
tls_listener.certificate_status.on_unavailable string What to do when a certificate's status cannot be established: refuse (default) refuses the connection, allow admits it and says so.
tls_listener.expiry_warning_fraction number A device certificate with less than this fraction of its own lifetime left is named once a day. Default 0.33, the last third of its life; 0 turns the warning off; it must be below 1. Needs a restart.

keys

Key Type Description
keys.<name>.current.version integer Version number of this key, 1 or greater and unique within the key. The key name must be lowercase letters, digits and _, starting with a letter, at most 64. This keys: block is for a port49d with no policy; with a policy, the policy file's keys: block replaces it and this one is refused. Needs a restart.
keys.<name>.current.secret_ref secret reference Where the shared secret is read from: env:NAME or file:/absolute/path. Required. The secret itself never appears in this file.
keys.<name>.current.retire_after string Not allowed on the current version: retire it by making another version current. On an accept version, the last day (UTC, YYYY-MM-DD) it still admits; midnight after that day it is refused. On a retired version it is refused.
keys.<name>.accept.[].version integer Version number of this key, 1 or greater and unique within the key. The key name must be lowercase letters, digits and _, starting with a letter, at most 64. This keys: block is for a port49d with no policy; with a policy, the policy file's keys: block replaces it and this one is refused. Needs a restart.
keys.<name>.accept.[].secret_ref secret reference Where the shared secret is read from: env:NAME or file:/absolute/path. Required. The secret itself never appears in this file.
keys.<name>.accept.[].retire_after string Not allowed on the current version: retire it by making another version current. On an accept version, the last day (UTC, YYYY-MM-DD) it still admits; midnight after that day it is refused. On a retired version it is refused.
keys.<name>.retired.[].version integer Version number of this key, 1 or greater and unique within the key. The key name must be lowercase letters, digits and _, starting with a letter, at most 64. This keys: block is for a port49d with no policy; with a policy, the policy file's keys: block replaces it and this one is refused. Needs a restart.
keys.<name>.retired.[].secret_ref secret reference Where the shared secret is read from: env:NAME or file:/absolute/path. Required. The secret itself never appears in this file.
keys.<name>.retired.[].retire_after string Not allowed on the current version: retire it by making another version current. On an accept version, the last day (UTC, YYYY-MM-DD) it still admits; midnight after that day it is refused. On a retired version it is refused.

limits

Key Type Description
limits.per_nas_connections integer Most simultaneous connections from one device address. Default 32; at least 1. Needs a restart.
limits.max_connections integer Global cap on simultaneous connections. Leave it out to size it from the open-file limit at start; otherwise 64 to 1048576. Needs a restart.
limits.idle_timeout duration How long a connection may wait for the next packet, and how long a write may take. Default 30s; must be greater than zero. Needs a restart.
limits.malformed_backoff_base duration A source that sends a malformed packet is held this long before its connection is closed; each further strike doubles the hold, up to malformed_backoff_max. Default 100ms; must be greater than zero. Needs a restart.
limits.malformed_backoff_max duration The ceiling of the malformed-packet hold. Default 5s; must be greater than zero. Needs a restart.
limits.malformed_window duration A source's strike count resets when no strike has arrived within this window. Default 10m; zero or negative is refused. Needs a restart.
limits.auth_failure_delay_base duration The delay before the FAIL reply to a failed authentication for one device and username; each further failure doubles it, up to auth_failure_delay_max. Default 500ms; must be greater than zero. Needs a restart.
limits.auth_failure_delay_max duration The ceiling of the failed-authentication delay. Default 2s; must be greater than zero. Keep it below your devices' shortest TACACS+ reply timeout: a FAIL that arrives after the device gave up reads as an unreachable server, and the device falls back to its local password. Needs a restart.
limits.auth_failure_window duration A device and username's failure count resets when no failure has arrived within this window. Default 10m; zero or negative is refused. Needs a restart.

accounting

Key Type Description
accounting.spool_capacity integer Reserved: it must be at least 1, and it does not bound the spool today, which is an append-only durable file. Default 10000. Needs a restart.
accounting.spool_path string Path of the accounting spool file. Default /var/lib/port49/acct.jsonl. It may not be the same file as broker.revocations_file. Needs a restart.

platforms

Key Type Description
platforms.<name>.session_services list of string Replaces the built-in list of authorization service values a session on this platform may arrive with. <name> must be a platform port49 ships an adapter for, or the start is refused. A fleet part-way through a service rename lists both names. Omit the key to take the default; an empty list, or an empty string in it, is refused because it would deny every session on that platform. Needs a restart.
platforms.<name>.enable_username string Moved to the platforms: block of the policy file, where port49 policy test reads it as port49d does. principal, the default, still loads with a notice; any other value is refused. Delete the line.

encryption

Key Type Description
encryption.current.id string Identifier of the key every token, receipt and context is sealed under at rest, 1 to 64 of A-Z a-z 0-9 . _ -. It is written into every record the key seals. encryption.current is required. Needs a restart.
encryption.current.secret_ref secret reference Where the key's 32 bytes are read from: env:NAME or file:/absolute/path. The secret must be exactly 32 bytes (AES-256). Needs a restart.
encryption.previous.id string Identifier of the previous key, kept so records sealed under it still open during a rotation. Optional, and it must differ from encryption.current.id: the id labels each record, so a rotation has to change it. Needs a restart.
encryption.previous.secret_ref secret reference Where the previous key's 32 bytes are read from, env:NAME or file:/absolute/path. Naming an id without it is refused. Retire the previous key by removing the block. Needs a restart.

session

Key Type Description
session.grace_window duration How long past session_expires a context is kept: the authorizer denies through it and then deletes the context, and the broker's refresh can resume inside it. Default 10m; must be greater than zero. Needs a restart.
session.ceiling_warning_platforms list of string Platforms you add to the list that receive the session-ceiling warning. Each is a platform name: lowercase letters, digits and _, starting with a letter. Not checked against the shipped platform set. Needs a restart.
session.index_ttl duration How long the context indexes (principal, session id, certificate) outlive a write. They let a revocation reach a live context. Default 12h; must be greater than zero. Needs a restart.
session.max_session_age_default duration The max_session_age a mint uses when the policy names none; session_expires is the authentication time plus this. Default 12h; must be greater than zero. Needs a restart.
session.ceiling_warning duration How long before session_expires a permitted reply carries the warning in server_msg. Default 30m; 0s turns the warning off; at most 24h. Needs a restart.
session.staleness_ceiling duration A context whose last refresh is older than this is denied context_stale. Default 4h. 0s turns the check off, and every start says so; otherwise at least 1m. With a broker in the same process, broker.refresh_interval may not exceed half of it. Needs a restart.

token

Key Type Description
token.candidate_cap integer How many unspent tokens may stand for one device and username at once. Past it the mint is refused with the count. Default 8; at least 1. Needs a restart.

broker

Key Type Description
broker.listen string Address the broker's HTTPS API listens on. Required when broker is present. Needs a restart.
broker.tls_cert_file string The broker's server certificate. Required. Read once, at start.
broker.tls_ca_bundle_file string Optional. The CAs clients are to trust for the server certificate, sent on GET /v1/config: the CA in use and, through a rotation, the next. A bundle that does not verify tls_cert_file is not sent. Read once, at start.
broker.tls_key_file string The server certificate's private key. Required. It must be owned by port49's user or root, with no group or other mode bit, not a symlink, and at most 1 MiB. Read once, at start.
broker.client_ca_file string Shorthand for one x509 human verifier: the one trust anchor client certificates are verified against, which must hold exactly one certificate. Use it or broker.identity, not both; with broker.identity, set trust_anchor_file on the x509 verifier instead. Read once, at start.
broker.identity.humans.[].type string Which kind of verifier this is. Under humans: x509, oidc or directory_password. Under machines: x509 (a workload certificate), oidc or k8s. Each type accepts only its own keys; another type's key is refused.
broker.identity.humans.[].trust_anchor_file string x509 only: the PEM file of the one trust anchor a chain is verified against. At most one x509 verifier per list, and the two lists may not name the same file. Read once, at start.
broker.identity.humans.[].issuer string oidc and k8s: the provider's issuer URL, https with no user information, query or fragment, spelled as the provider spells it in its discovery document. Required.
broker.identity.humans.[].client_id string oidc under humans, required: the public client's id, which a person's ID token names in aud.
broker.identity.humans.[].principal_claim string oidc under humans, required, with no default: preferred_username, email or upn. Choose the claim only your provider's administrators can set.
broker.identity.humans.[].principal_domains list of string oidc and directory_password under humans, required: the domains whose people this verifier may name (user:<name>@<domain>), each a name with a dot and no @, lower-cased. Matched exactly; no subdomain is implied. Two verifiers at different issuers may not share a domain, and a directory_password verifier may not share one with an oidc verifier.
broker.identity.humans.[].scopes list of string oidc under humans: extra scopes to request beyond openid, each printable ASCII with no space.
broker.identity.humans.[].algorithms list of string oidc and k8s: the token signature algorithms accepted, from RS256, PS256, ES256, ES384 and EdDSA. Default RS256. none and the HS algorithms are refused.
broker.identity.humans.[].ca_file string oidc and k8s: PEM CA for the provider's TLS certificate. Optional; without it the system roots are used. It must be a readable file.
broker.identity.humans.[].second_factor string directory_password only, required: totp, the one second factor a password sign-in takes.
broker.identity.humans.[].filter string directory_password only: an LDAP filter, which must compile, that a person's entry must also match to sign in.
broker.identity.humans.[].enrollment.open_until string directory_password only: until this RFC 3339 time (for example 2026-12-01T00:00:00Z), a person with no authenticator enrolled may enroll one by signing in with their directory password. Outside every enrollment window the sign-in is refused.
broker.identity.humans.[].enrollment.new_entries_for duration directory_password only: a person whose directory entry was created less than this long ago may enroll. Default 336h (14 days); 0s turns it off; at most 2160h (90 days).
broker.identity.humans.[].enrollment.reopen_for duration directory_password only: how long a person's enrollment window stays open after an administrator resets their second factor, and how long reset-second-factor --all reopens enrollment. Default 24h; 1h to 168h.
broker.identity.humans.[].rate_limits.failures_per_principal integer directory_password only: failed sign-ins allowed per person within window. Default 5; 1 to 100.
broker.identity.humans.[].rate_limits.failures_per_source integer directory_password only: failed sign-ins allowed per source address within window. Default 50; 0 to 10000.
broker.identity.humans.[].rate_limits.window duration directory_password only: the window the failure limits count over. Default 15m; 1m to 24h.
broker.identity.humans.[].totp_label string directory_password only: the issuer name an authenticator app shows. Printable ASCII, no colon, at most 64 bytes.
broker.identity.machines.[].type string Which kind of verifier this is. Under humans: x509, oidc or directory_password. Under machines: x509 (a workload certificate), oidc or k8s. Each type accepts only its own keys; another type's key is refused.
broker.identity.machines.[].trust_anchor_file string x509 only: the PEM file of the one trust anchor a chain is verified against. At most one x509 verifier per list, and the two lists may not name the same file. Read once, at start.
broker.identity.machines.[].spiffe_trust_domains list of string x509 only, and required: the SPIFFE trust domains whose workloads this anchor may name (spiffe://<trust domain>/<path>), each written as the domain is, never folded to lower case.
broker.identity.machines.[].issuer string oidc and k8s: the provider's issuer URL, https with no user information, query or fragment, spelled as the provider spells it in its discovery document. Required.
broker.identity.machines.[].audience string Required for oidc (the API identifier a client-credentials token names in aud) and for k8s (the audience your pods' projected token volume asks for).
broker.identity.machines.[].clients.[].client_id string oidc only: a client id that may be a machine principal (app:<client id>). Required, and not repeated.
broker.identity.machines.[].clients.[].sub string oidc only: the client's own identity at the provider, which its client-credentials tokens carry as sub and no person's token does. Required.
broker.identity.machines.[].algorithms list of string oidc and k8s: the token signature algorithms accepted, from RS256, PS256, ES256, ES384 and EdDSA. Default RS256. none and the HS algorithms are refused.
broker.identity.machines.[].ca_file string oidc and k8s: PEM CA for the provider's TLS certificate. Optional; without it the system roots are used. It must be a readable file.
broker.identity.machines.[].cluster string k8s only, required: this cluster's name in port49's records and log lines. Lowercase letters, digits and _, starting with a letter, at most 64; one name per cluster.
broker.identity.machines.[].jwks string k8s only, required: the https URL the cluster's signing keys are read from, typically the API server's /openid/v1/jwks.
broker.identity.machines.[].namespaces list of string k8s only, required: the namespaces whose service accounts this cluster may name, by name.
broker.identity.machines.[].directory.kind string x509 only: how a workload certificate finds its directory entry. Must be spiffe_id. Needs broker.directory.
broker.identity.machines.[].directory.attribute string x509 only: the directory attribute that holds the SPIFFE ID. Required, no default, and not the attribute broker.directory uses for username_attribute or principal_attribute.
broker.identity.machines.[].directory.allow_weak boolean x509 only: acknowledges that this mapping is weak. Required to be true, with issuers.
broker.identity.machines.[].directory.issuers list of string x509 only: the RFC 4514 issuer DNs the workload certificate's issuer must be one of. Required.
broker.identity.machines.[].directory.user_filter string x509 only: the LDAP filter for the entry lookup. Default broker.directory.user_filter.
broker.identity.clock_skew duration How far a token's time claims may differ from the broker's clock. Default 1m; 0s to 5m. Needs a restart.
broker.identity.max_token_age duration The oldest an ID token may be, by its issue time, and still be accepted. Default 2h; 5m to 24h. Needs a restart.
broker.identity.jwks_refresh_interval duration How often each provider's signing keys are re-fetched. Default 1h; 5m to 24h. Needs a restart.
broker.identity.jwks_max_age duration How long fetched keys are trusted without a successful refresh. Default 24h; 10m to 720h; at least twice jwks_refresh_interval, so one missed refresh does not refuse every token. Needs a restart.
broker.identity.issuance.certificate_lifetime duration Lifetime of the certificate the broker issues after a sign-in. Default 12h; 1h to 24h. Writing issuance: {} turns issuance on with this default. Issuance needs a human oidc or directory_password verifier, and a directory_password sign-in needs it. Needs a restart.
broker.identity.per_login_mfa.hardware_key_touch.attestation_roots_file string The PEM roots a touch key's attestation chains to. Required when hardware_key_touch is present. SIGHUP re-reads it; a file that does not load keeps the roots in force.
broker.identity.per_login_mfa.idp_mfa.amr list of string Authentication method references the identity provider's ID token may carry as proof of the second factor. Default [mfa]; none may be empty. Counts only beside an x509 humans verifier. Needs a restart.
broker.identity.per_login_mfa.idp_mfa.acr_values string The acr_values the sign-in sends to the provider. Needs a restart.
broker.policy_file string Path of the policy file this process admits from. Required. It is watched every policy_watch_interval and re-read at SIGHUP; a file that does not load is refused and the policy in force keeps serving. The file and its directory must be writable by port49's user or root alone.
broker.batch_max integer The most devices in one mint request; a larger sweep pages. Default 100; 1 to 1000. Needs a restart.
broker.policy_watch_interval duration How often the policy file is read for a change. A change is loaded once two reads in a row agree, so an edit takes effect within two intervals and a write in progress is not loaded part-way. Default 5s; 1s to 5m. SIGHUP loads what is on disk at once. Write the file atomically: a temporary file renamed over it. A change to this key needs a restart.
broker.revocations_file string port49d.yaml only (refused in port49-broker.yaml): where port49d keeps the revocations port49 revoke puts in force, so a restart forgets none. Required in a broker: block, which port49d.yaml accepts only on the memory store. Its directory must exist and be writable by port49's user or root alone, and the name may not be a symlink. port49d never creates it: run port49d init-revocations once per install. port49d rewrites it, mode 0600 and atomically, after every revocation and lift. Needs a restart.
broker.max_conns_per_principal integer The most broker connections one principal may hold; a new one closes the oldest idle one, and is refused with 503 when all are busy. Default 4; 1 to 64. Needs a restart.
broker.max_pending_conns_per_source integer The most broker connections one address may hold that are not yet bound to a principal; the next is reset. Default 16; 1 to 256. Needs a restart.
broker.certificate_status.disabled boolean Set true to check no revocation status. Then none of crls, ocsp, refresh_interval and on_unavailable may be set. The block is required: leaving it out is refused, so skipping the check is always a written choice. Needs a restart.
broker.certificate_status.crls.[].name string Name of this revocation list source: lowercase letters, digits and _, starting with a letter, at most 64, and unique in the block. It names the kept file crl-<name>.der and the crl_next_update metric label.
broker.certificate_status.crls.[].file string Path of a CRL file. Set exactly one of file and url.
broker.certificate_status.crls.[].url string An http or https URL to fetch the CRL from. Set exactly one of file and url.
broker.certificate_status.crls.[].issuer_file string Required. The CA certificate that signs the list: the trust anchor, or a certificate the anchor signed.
broker.certificate_status.ocsp.responder string An http or https URL of a fixed OCSP responder. Set this or use_aia: true.
broker.certificate_status.ocsp.use_aia boolean Set true to ask each certificate's own OCSP responder, from its Authority Information Access extension. Set this or responder.
broker.certificate_status.ocsp.timeout duration How long one OCSP request may take. Default 2s; 100ms to 10s.
broker.certificate_status.ocsp.hash string The hash of the OCSP CertID: sha256 (default) or sha1.
broker.certificate_status.refresh_interval duration How often the revocation lists are fetched or re-read. Default 10m; 1m to 24h.
broker.certificate_status.on_unavailable string What to do when a certificate's status cannot be established: refuse (default) refuses the connection, allow admits it and says so.
broker.state_dir string port49-broker.yaml only (refused as a key of port49d.yaml): absolute path of a directory that exists and that neither its group nor others can write (create it mode 0700), where port49-broker keeps the CRLs it verified and its other state. Required under the broker's role, which has no revocations file. It may not be inside inventory.service.state_dir or mirror_dir, nor hold them. Needs a restart.
broker.directory.servers.[].uri string An ldaps:// or ldap:// URI with a host, and no credentials in it. Plain ldap:// needs start_tls: true. Two servers may not share host and port. At least one server is required. Needs a restart.
broker.directory.servers.[].priority integer Among servers of the preferred site, the one with the higher number is preferred. Default 0. Needs a restart.
broker.directory.servers.[].site string The site this server belongs to. When the broker's own site is known, servers of that site are tried first. Needs a restart.
broker.directory.servers.[].start_tls boolean Upgrade an ldap:// connection with StartTLS. Refused on an ldaps:// URI. Needs a restart.
broker.directory.site string The site this broker runs in. Overrides site_env. Servers whose site matches are preferred. Needs a restart.
broker.directory.site_env string Name of the environment variable that holds the site when site is not set. Default PORT49_SITE. It may not be the variable bind.password_secret_ref reads, because the site is logged. Needs a restart.
broker.directory.ca_file string PEM CA that verifies the directory servers' certificates. Required, and it must be readable. Needs a restart.
broker.directory.bind.mechanism string How the broker authenticates to the directory: external (a TLS client certificate), gssapi (Kerberos) or simple (a DN and password). Required. Needs a restart.
broker.directory.bind.tls_cert_file string external only, required: the client certificate. Needs a restart.
broker.directory.bind.tls_key_file string external only, required: the client certificate's key. It must be owned by port49's user or root, with no group or other mode bit. Needs a restart.
broker.directory.bind.keytab_file string gssapi only, required: the Kerberos keytab. Needs a restart.
broker.directory.bind.principal string gssapi only, required: the Kerberos principal to bind as. Needs a restart.
broker.directory.bind.krb5_conf string gssapi only, required: path of the krb5.conf to use. Needs a restart.
broker.directory.bind.bind_dn string simple only, required: the DN to bind as. Needs a restart.
broker.directory.bind.password_secret_ref secret reference simple only, required: where the bind password is read from, env:NAME or file:/absolute/path. It is read when the directory opens, not when the file is checked. Needs a restart.
broker.directory.base_dn string Required. The base DN people are searched under; it must parse as an RFC 4514 DN. Needs a restart.
broker.directory.groups_base_dn string The base DN groups are searched under. Default base_dn. Needs a restart.
broker.directory.user_filter string LDAP filter for a person's entry; it must compile. Default (objectClass=person). Needs a restart.
broker.directory.group_filter string LDAP filter for a group; it must compile. Default (\|(objectClass=group)(objectClass=groupOfNames)). Needs a restart.
broker.directory.principal_attribute string The attribute that holds a person's principal. Default userPrincipalName. Must be an attribute name or OID. Needs a restart.
broker.directory.username_attribute string The attribute that holds the username a device sees. Default sAMAccountName. Needs a restart.
broker.directory.group_name_attribute string The attribute that names a group. Default cn. Needs a restart.
broker.directory.member_attribute string The attribute that lists a group's members. Default member. Needs a restart.
broker.directory.certificate_mapping.kind string How a client certificate finds its entry: issuer_serial, sid, san_upn, san_email, subject_dn or cn. Required when an x509 human verifier is configured, optional otherwise. issuer_serial and sid are strong; the others are weak and need allow_weak and issuers. Needs a restart.
broker.directory.certificate_mapping.attribute string The entry attribute the mapping looks up. Default by kind: altSecurityIdentities for issuer_serial, objectSid for sid, userPrincipalName for san_upn, mail for san_email, distinguishedName for subject_dn, cn for cn. Pointing a strong kind at another attribute makes it weak. Needs a restart.
broker.directory.certificate_mapping.allow_weak boolean Acknowledges a weak mapping. Required true for a weak kind or a repointed strong one, with issuers. Needs a restart.
broker.directory.certificate_mapping.issuers list of string RFC 4514 issuer DNs the mapping is pinned to. Required for a weak or repointed mapping. Needs a restart.
broker.directory.nested_groups string How nested group membership is resolved. Required: in_chain (Active Directory's chain matching), recursive (port49 walks member_attribute) or direct (groups are flat). Needs a restart.
broker.directory.max_depth integer How deep recursive nesting is followed. Default 16; 1 to 64. Needs a restart.
broker.directory.query_timeout duration How long one directory query may take. Default 5s; 1s to 30s. Needs a restart.
broker.directory.health_check.interval duration How often each server is checked. Default 30s; 5s to 5m. Needs a restart.
broker.directory.health_check.timeout duration How long one check may take; at most interval. Default 2s. Needs a restart.
broker.directory.circuit_breaker.failure_threshold integer Consecutive failures that open a server's circuit. Default 3; 1 to 20. Needs a restart.
broker.directory.circuit_breaker.probe_interval duration How often an open circuit is probed. Default 1m; 5s to 10m. Needs a restart.
broker.directory.failback.stability_window duration How long a preferred server must stay healthy before the broker moves back to it. Default 5m; 0s to 1h. Needs a restart.
broker.directory.grace duration When no directory server answers, the membership stored from the last good lookup is used if it was verified no longer ago than this; older than this, the principal is refused directory_unavailable. A refusal the directory already gave is never overruled. Default 4h; 0s to 24h; not above session.staleness_ceiling when both run in one process. Needs a restart.
broker.directory.prewarm.interval duration How often the broker pre-verifies every known principal. Default 1h; 0s turns it off; otherwise 5m to 24h. Needs a restart.
broker.directory.prewarm.max_principals integer The most principals one pre-warm pass verifies. People and, under a workload mapping, workloads each take up to it, so one interval verifies up to twice it. Default 10000; 1 to 1000000. Needs a restart.
broker.refresh_interval duration How often the broker re-verifies every live principal and stamps refreshed_at. Default 5m; 1m to 1h. In one process with session.staleness_ceiling it may not exceed half of it. Needs a restart.

lab

Key Type Description
lab.session_attributes list of string Test lab only; leave it out in production. Attributes attached to a permitted session authorization reply of a seeded context. Not accepted in port49-broker.yaml. Needs a restart.
lab.command_attributes list of string Test lab only. Attributes attached to a permitted per-command authorization reply, a separate list from session_attributes because the lab asks the same question of each reply separately. Not accepted in port49-broker.yaml. Needs a restart.
lab.deny_prefixes list of string Test lab only. Commands the lab denies by prefix, matched on a token boundary: show denies show version but not shower. Not accepted in port49-broker.yaml. Needs a restart.
lab.fault_file string Test lab only. A file naming the fault in force. Its contents are read each time the switch is checked, so editing it takes effect at once; an absent file means no fault. Not accepted in port49-broker.yaml. The path itself needs a restart.
lab.seed_token_file string Test lab only. Where port49d writes the token pairs it mints for seed_tokens, one JSON object per line, mode 0600. Required when seed_tokens is non-empty. Needs a restart.
lab.seed_tokens.[].nas string Test lab only. The device address the seeded token is for. Required.
lab.seed_tokens.[].username string Test lab only. The username the seeded token is for. Required.
lab.seed_tokens.[].enable boolean Test lab only. true also mints an enable token beside the login one.
lab.deny_message_length integer Test lab only. Pads the lab's deny server_msg to this many bytes with a counting pattern. 0 leaves it; otherwise 16 to 4096. Not accepted in port49-broker.yaml. Needs a restart.
lab.deny_session boolean Test lab only. Fails every session authorization of a seeded context with the padded deny message. Not accepted in port49-broker.yaml. Needs a restart.

inventory

Key Type Description
inventory.max_removed_percent integer The most devices, as a percentage of the devices in force, an inventory reload may take out of service (removed, or kept and newly refused). A reload that takes more is refused and the previous inventory keeps serving. Default 10; 0 refuses every removal; 100 turns the bound off. An empty inventory is refused whatever this says. SIGHUP re-reads this key, so a deliberate large removal needs no restart: raise it, send SIGHUP, lower it again.
inventory.service.url string Where port49d takes its inventory from when the inventory service publishes it, in place of inventory_file: https://host[:port] with no user information, path, query or fragment. Needs a policy. Needs a restart.
inventory.service.ca_file string Absolute path of the PEM CA that verifies the service's certificate. Required; without it any public CA could vouch for the service. Needs a restart.
inventory.service.tls_cert_file string Absolute path of port49d's client certificate for the service, presented over mutual TLS. Required. Needs a restart.
inventory.service.tls_key_file string Absolute path of the client certificate's key. Required; owned by port49's user or root, no group or other mode bit. Needs a restart.
inventory.service.verify_key_files list of string Absolute paths of the service's signing public keys: two while its key rotates. At least one is required. Every document, from the service or from disk, is verified against them. SIGHUP re-reads the list.
inventory.service.poll_interval duration How often port49d asks the service for the bundle's version. Default 30s; 5s to 10m. Needs a restart.
inventory.service.state_dir string Absolute directory that holds the last document port49d applied, so a restart starts from it with no service. Required, and outside mirror_dir. Needs a restart.
inventory.service.mirror_dir string Optional absolute directory of the shared copy the service writes, read at a cold start when state_dir has none. Outside state_dir. Needs a restart.

stores

Key Type Description
stores.directory.backend string Where records live: memory (default) or etcd. A store block on etcd also needs its etcd block; etcd in port49d.yaml needs policy: and no broker: block, and port49-broker.yaml must use etcd. Needs a restart.
stores.directory.packet_deadline duration Bounds all store work for one TACACS+ packet, so a device hears an answer before its own timeout. Default 2s; 100ms to 2s; for the session store at least etcd.call_timeout. Needs a restart.
stores.directory.etcd.endpoints list of string etcd endpoints, each https://host:port with no user information, path, query or fragment. At least one. port49 authenticates to etcd by client certificate.
stores.directory.etcd.ca_file string Absolute path of the PEM CA that verifies etcd's certificate. Required.
stores.directory.etcd.prefix string The keyspace prefix. Default /port49/; it must start with /.
stores.directory.etcd.daemon.cert_file string port49d's client certificate for etcd. Required in port49d.yaml on etcd, refused in port49-broker.yaml. Absolute path. Its CN is the etcd user, and a CN of root is refused.
stores.directory.etcd.daemon.key_file string The key of port49d's etcd client certificate, absolute path, owned by port49's user or root with no group or other mode bit.
stores.directory.etcd.broker.cert_file string port49-broker's client certificate for etcd. Required in port49-broker.yaml, refused in port49d.yaml. Absolute path; a CN of root is refused.
stores.directory.etcd.broker.key_file string The key of port49-broker's etcd client certificate, absolute path, owned by port49's user or root with no group or other mode bit.
stores.directory.etcd.roles string enforced (default) or unenforced: whether port49d refuses to start when its etcd identity can do more than its role.
stores.directory.etcd.call_timeout duration Bounds each token and context call to etcd. Default 500ms; 10ms to 5s.
stores.directory.etcd.bulk_timeout duration Bounds each bulk scan. Default 30s; 1s to 5m.
stores.directory.etcd.breaker.failures integer Consecutive failures that open a pool's circuit. Default 5; 1 to 100.
stores.directory.etcd.breaker.open_for duration How long an opened circuit stays open. Default 5s; 1s to 5m.
stores.directory.etcd.max_in_flight.token integer In-flight token calls. Default 64; 1 to 4096. A call that gets no slot within its deadline is refused, never queued.
stores.directory.etcd.max_in_flight.context integer In-flight context calls. Default 128; 1 to 4096.
stores.directory.etcd.max_in_flight.bulk integer In-flight bulk scans. Default 4; 1 to 4096.
stores.directory.etcd.lease_grace duration How long past a record's own expiry its lease reclaims it. Default 5m; 1m to 1h.
stores.directory.etcd.hold_ttl duration The keyspace hold's lease, which is also how long a pool's circuit stays open before the store is gone. Default 15s; 10s to 60s.
stores.directory.etcd.sweep_interval duration How often the index sweep runs. Default 10m; 1m to 24h.
stores.directory.etcd.start_timeout duration How long port49d waits for the store at start. Default 30s; 0 waits for as long as it takes (a bare 0 is accepted); otherwise 5s to 10m.
stores.directory.etcd.health.interval duration port49d.yaml only (refused in port49-broker.yaml): how often the store is checked. Default 1s; 200ms to 10s; for the session store at least call_timeout.
stores.directory.etcd.health.close_after integer port49d.yaml only: consecutive failed checks after which every listener closes, so devices use their local method. Default 5; 2 to 60.
stores.directory.etcd.health.open_after integer port49d.yaml only: consecutive passing checks after which the listeners open again. Default 3; 1 to 60.
stores.directory.etcd.context_cache.ttl duration port49d.yaml only (refused in port49-broker.yaml): how long command and session authorization may read a context from the cache, which adds to revocation latency. Required when the block is present; 100ms to 1m. Leave the block out for no cache.
stores.directory.etcd.context_cache.max_entries integer port49d.yaml only: the most contexts the cache holds. Default 65536; 1 to 1048576.
stores.session.backend string Where records live: memory (default) or etcd. A store block on etcd also needs its etcd block; etcd in port49d.yaml needs policy: and no broker: block, and port49-broker.yaml must use etcd. Needs a restart.
stores.session.packet_deadline duration Bounds all store work for one TACACS+ packet, so a device hears an answer before its own timeout. Default 2s; 100ms to 2s; for the session store at least etcd.call_timeout. Needs a restart.
stores.session.etcd.endpoints list of string etcd endpoints, each https://host:port with no user information, path, query or fragment. At least one. port49 authenticates to etcd by client certificate.
stores.session.etcd.ca_file string Absolute path of the PEM CA that verifies etcd's certificate. Required.
stores.session.etcd.prefix string The keyspace prefix. Default /port49/; it must start with /.
stores.session.etcd.daemon.cert_file string port49d's client certificate for etcd. Required in port49d.yaml on etcd, refused in port49-broker.yaml. Absolute path. Its CN is the etcd user, and a CN of root is refused.
stores.session.etcd.daemon.key_file string The key of port49d's etcd client certificate, absolute path, owned by port49's user or root with no group or other mode bit.
stores.session.etcd.broker.cert_file string port49-broker's client certificate for etcd. Required in port49-broker.yaml, refused in port49d.yaml. Absolute path; a CN of root is refused.
stores.session.etcd.broker.key_file string The key of port49-broker's etcd client certificate, absolute path, owned by port49's user or root with no group or other mode bit.
stores.session.etcd.roles string enforced (default) or unenforced: whether port49d refuses to start when its etcd identity can do more than its role.
stores.session.etcd.call_timeout duration Bounds each token and context call to etcd. Default 500ms; 10ms to 5s.
stores.session.etcd.bulk_timeout duration Bounds each bulk scan. Default 30s; 1s to 5m.
stores.session.etcd.breaker.failures integer Consecutive failures that open a pool's circuit. Default 5; 1 to 100.
stores.session.etcd.breaker.open_for duration How long an opened circuit stays open. Default 5s; 1s to 5m.
stores.session.etcd.max_in_flight.token integer In-flight token calls. Default 64; 1 to 4096. A call that gets no slot within its deadline is refused, never queued.
stores.session.etcd.max_in_flight.context integer In-flight context calls. Default 128; 1 to 4096.
stores.session.etcd.max_in_flight.bulk integer In-flight bulk scans. Default 4; 1 to 4096.
stores.session.etcd.lease_grace duration How long past a record's own expiry its lease reclaims it. Default 5m; 1m to 1h.
stores.session.etcd.hold_ttl duration The keyspace hold's lease, which is also how long a pool's circuit stays open before the store is gone. Default 15s; 10s to 60s.
stores.session.etcd.sweep_interval duration How often the index sweep runs. Default 10m; 1m to 24h.
stores.session.etcd.start_timeout duration How long port49d waits for the store at start. Default 30s; 0 waits for as long as it takes (a bare 0 is accepted); otherwise 5s to 10m.
stores.session.etcd.health.interval duration port49d.yaml only (refused in port49-broker.yaml): how often the store is checked. Default 1s; 200ms to 10s; for the session store at least call_timeout.
stores.session.etcd.health.close_after integer port49d.yaml only: consecutive failed checks after which every listener closes, so devices use their local method. Default 5; 2 to 60.
stores.session.etcd.health.open_after integer port49d.yaml only: consecutive passing checks after which the listeners open again. Default 3; 1 to 60.
stores.session.etcd.context_cache.ttl duration port49d.yaml only (refused in port49-broker.yaml): how long command and session authorization may read a context from the cache, which adds to revocation latency. Required when the block is present; 100ms to 1m. Leave the block out for no cache.
stores.session.etcd.context_cache.max_entries integer port49d.yaml only: the most contexts the cache holds. Default 65536; 1 to 1048576.

policy

Key Type Description
policy.file string Path of the policy file a port49d with no broker: block admits from. Required when policy: is present. Refused beside a broker: block, whose policy is broker.policy_file. Re-read every watch_interval and at SIGHUP.
policy.watch_interval duration How often the policy file is read for a change. Default 5s; 1s to 5m. A change to this key needs a restart.

key_secret_refs

Key Type Description
key_secret_refs.env_prefix string A policy's key secret_ref: env:NAME may name only variables that begin with this prefix, so a policy writer cannot pick which of the host's secrets becomes a TACACS+ key. Default PORT49_KEY_; at least 4 characters of a variable name, ending in _. Needs a restart.
key_secret_refs.file_dir string A policy's key secret_ref: file:/path may name only files under this directory. Default /run/port49/keys; an absolute, clean path below /. Needs a restart.

port49-broker.yaml

port49-broker.yaml has the schema above, read under the broker's role. The broker never speaks TACACS+ and never holds a device key, so the loader refuses the keys below by name: a file that names one does not load, and the error names the key. A few other keys are refused by the check of their own block rather than by this table; the entries above mark them "port49d.yaml only".

Refused key Why
listen port49-broker never speaks TACACS+; that is port49d's
tls_listener port49-broker never speaks TACACS+; that is port49d's
keys device keys are the policy file's keys: block
key_secret_refs port49-broker reads key names, never a key's secret
accounting accounting is port49d's
limits TACACS+ limits are port49d's
receipt_idle_ttl receipts are port49d's
receipt_max_age receipts are port49d's
platforms session services are port49d's
policy port49-broker's policy is broker.policy_file
broker.revocations_file on etcd the store keeps the marks (decision 36); remove it
stores.directory.etcd.daemon the daemon's etcd identity is port49d's alone
stores.session.etcd.daemon the daemon's etcd identity is port49d's alone
lab.fault_file the lab's fault switch is port49d's
lab.session_attributes the lab's answers are port49d's
lab.command_attributes the lab's answers are port49d's
lab.deny_prefixes the lab's answers are port49d's
lab.deny_message_length the lab's answers are port49d's
lab.deny_session the lab's answers are port49d's