Documentation menu

Configuration

Agent configuration

secretserver-agent has no configuration file and reads no environment variables. Everything is set with command-line flags, and enrollment state lives in a private state directory.

Commands

secretserver-agent login|status|access|render|run [options]
CommandPurpose
loginGenerate the device key and enroll with an API key file or OAuth2 device approval
statusPrint identity and grant metadata as JSON
accessCall one assigned grant by alias and write the result (may contain secrets) to stdout
renderResolve %%NAME%% tokens in text or JSON through the device identity
runKeep secret.read and variable.resolve grants refreshed as private JSON files

Common flag

FlagDefaultNotes
--state-dir~/.config/secretserver-agentAbsolute, clean path to a 0700 directory that is not a symlink. Created if missing. With no home directory, it must be given explicitly.

Flags go after the command name, for example secretserver-agent run --state-dir /var/lib/secretserver-agent ....

login flags

FlagDefaultNotes
--serverhttps://api.secretserver.ioHTTPS origin with no path, query or credentials
--accountrequiredExpected account UUID, canonical lowercase
--profilerequiredAdmin-assigned access profile UUID
--namerequiredDevice name: 1 to 128 letters, digits, dots, underscores or hyphens, starting with a letter or digit
--api-key-filenot setRegular 0600 file with one account-admin API key (up to 64 KiB). Omit it to use OAuth2 device approval.
--allow-loopback-httpfalseDevelopment only: allow http to a literal loopback IP. Saved with the identity.

login refuses to replace a completed identity. If a pending identity exists, its server, account, profile, name and HTTP setting must match the new command.

status, access, render and run flags

FlagDefaultUsed byNotes
--aliasnot setaccessGrant alias to call
--inputnot setaccess, renderRequest JSON file for access (2 MiB max, must be valid JSON) or template file for render (1 MiB max). - reads stdin. Without it, access sends {} and render reads stdin.
--jsonfalserenderResolve string values inside a JSON document instead of raw text
--output-dirrequired for runrunDedicated delivery directory; must be separate from the state directory in both directions
--poll30srunRefresh interval, 1s to 1h

Files and permissions

PathContents
<state-dir>/identity.jsonserver, allow_loopback_http, account_id, profile_id, device_id, name and the Ed25519 private_key. Mode 0600; unknown fields are rejected.
<state-dir>/.lockPID of the process holding the directory. Left behind after a crash; check that the process is gone before removing it.
<output-dir>/.secretserver-agentOwnership marker (server, account, device). A directory marked by another agent is refused.
<output-dir>/<alias>.jsonThe grant's data fields as JSON, mode 0600, replaced atomically
<output-dir>/.lockLock for the run loop

The output directory must be empty on first use and mode 0700. Treat it as agent-managed: stale .json files are removed on each refresh, on failure, on shutdown and at restart before any network call. Aliases that differ only in case fail the refresh.

The device key is an ordinary file. Copying it copies the device identity. Use a tmpfs output directory if delivered files must not survive a power loss.

Profile grants

Admins configure what a device may use with PUT /api/v1/agents/profiles/{UUID} (requires admin:*). Resources must belong to the same account; wildcards are not allowed.

serviceresourceBehavior
secret.readsecret UUIDDelivered by run; readable with access
variable.resolvevariable UUIDDelivered by run as {"value": ...}; used by render
key.signkey handle, plus backendaccess with an input of {"message":"BASE64","purpose":"..."}; never exports key material
database.issuedatabase role UUIDaccess issues database credentials on demand; run never does

profile body

{
  "name": "web-production",
  "enabled": true,
  "grants": [
    {"alias": "app-config", "service": "secret.read", "resource": "SECRET_UUID"},
    {"alias": "release-signing", "service": "key.sign", "backend": "softhsm", "resource": "KEY_HANDLE"},
    {"alias": "database", "service": "database.issue", "resource": "DATABASE_ROLE_UUID"},
    {"alias": "sudo-password", "service": "variable.resolve", "resource": "VARIABLE_UUID"}
  ]
}

Admin endpoints

MethodEndpointPurpose
PUT/api/v1/agents/profiles/{UUID}Create or update a profile
GET/api/v1/agents/profilesList profiles and grants
POST/api/v1/agents/devicesAPI-key enrollment
GET/api/v1/agents/devicesList enrolled devices
DELETE/api/v1/agents/devices/{UUID}Revoke a device

Network behavior

SettingValue
TLS1.3 minimum, certificates verified against system roots
RedirectsRefused
HTTP timeout15 seconds per request
Refresh cycleBounded to 30 seconds; a failure clears delivered files
Response limit4 MiB
Signed headersX-SecretServer-Account, -Device, -Time, -Nonce, -Signature
Clock skewServer accepts proofs up to 60 seconds old and 5 seconds in the future
OAuth2RFC 8628 device flow, client ID secretserver-agent; the token is scoped agent:enroll

A 401 or 403 during run stops the agent. Other failures retry at the polling interval.

Server-side settings

SettingDefaultNotes
AGENT_VERIFICATION_URIhttps://secretserver.io/agent/authorizeApproval page URL shown to users during OAuth2 login. Set it when self-hosting.
NEXT_PUBLIC_API_URLnoneWeb approval page setting; point it at the same API
Migration034_agents.sqlApplied on API startup if missing

TLS termination in front of the API must keep the request path and query exactly, because they are signed.

systemd unit

deploy/secretserver-agent.service (excerpt)

[Service]
Type=simple
User=secretserver-agent
Group=secretserver-agent
StateDirectory=secretserver-agent
StateDirectoryMode=0700
RuntimeDirectory=secretserver-agent
RuntimeDirectoryMode=0700
UMask=0077
ExecStart=/usr/local/bin/secretserver-agent run --state-dir /var/lib/secretserver-agent --output-dir /run/secretserver-agent --poll 30s
Restart=on-failure
RestartSec=15
NoNewPrivileges=true
CapabilityBoundingSet=
ProtectSystem=strict
ProtectHome=true
PrivateDevices=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=true
SystemCallFilter=@system-service

systemd removes /run/secretserver-agent whenever the service stops. After revocation the agent exits with status 1 and systemd retries every 15 seconds; disable the unit on a decommissioned host.

Exit codes

CodeMeaning
0Success, or -h / --help
1Any error, printed as secretserver-agent: <message>