Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
"title": "Install the Smallstep Agent",
"path": "/platform/smallstep-agent.mdx"
},
{
"title": "Credential Files",
"path": "/platform/credential-files.mdx"
},
{
"title": "Troubleshooting Guide",
"path": "/platform/troubleshooting-agent.mdx"
Expand Down
160 changes: 160 additions & 0 deletions platform/credential-files.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
updated_at: October 23, 2026
title: Credential Files
html_title: Write Credential Files Where Applications Expect Them
description: Configure where the Smallstep Agent writes a credential's key, certificate, and root CA, with per-file owners, modes, POSIX ACLs, and Windows access lists.
---

A managed credential can tell the Smallstep Agent where to write its private key, certificate, and root CA certificate, and who may read each one.
Applications such as PostgreSQL, Elasticsearch, or SQL Server then read their credential directly, without a hook that copies files and fixes their permissions after every renewal.

<Alert severity="info">
<div>
Credential files need Smallstep Agent 0.72.0 or later, and API version `2026-11-01`.
Older agents don't apply most of these settings; update the agent before you use them.
</div>
</Alert>

## How it works

A credential's `files` object holds one block per platform: `linux`, `macos`, and `windows`.
Each device receives only the block for its own operating system.
Inside a block, the `key`, `certificate`, and `root` entries each take their own path and permissions.

Every field is optional.
A platform without a block, or a file without an entry, keeps the agent's default path and permissions.

## PostgreSQL on Linux

PostgreSQL refuses to start when its key is readable by anyone but its own account.
This credential writes the key for `postgres` alone, the certificate chain readable to all, and the root CA readable to a `monitoring` group:

```bash
curl -X POST https://gateway.smallstep.com/api/credentials \
-H "Authorization: Bearer $SMALLSTEP_API_TOKEN" \
-H "X-Smallstep-Api-Version: 2026-11-01" \
-H "Content-Type: application/json" \
-d @postgres-credential.json
```

```json
{
"slug": "postgres-server",
"certificate": {
"type": "X509",
"authorityID": "3c2e1115-49b7-4657-9b0f-301d7111bc6e",
"duration": "24h0m0s",
"fields": { "commonName": { "static": "db.example.internal" } }
},
"key": { "type": "ECDSA_P256", "protection": "NONE", "store": "MACHINE" },
"policy": { "operatingSystem": ["Linux"] },
"files": {
"linux": {
"key": {
"path": "/var/lib/postgresql/16/main/server.key",
"format": "PKCS8",
"owner": "postgres", "group": "postgres", "mode": "0600"
},
"certificate": {
"path": "/var/lib/postgresql/16/main/server.crt",
"contents": "CHAIN",
"owner": "postgres", "group": "postgres", "mode": "0644"
},
"root": {
"path": "/var/lib/postgresql/16/main/root.crt",
"mode": "0640",
"acl": [{ "type": "GROUP", "name": "monitoring", "access": "r" }]
}
}
}
}
```

## A service account on Windows

On Windows, each file takes an access list of principals instead of an owner and mode.
This credential lets the `CORP\svc-sql` domain account read the key, and nobody else besides `SYSTEM` and the Administrators:

```json
{
"slug": "sql-server",
"certificate": { "type": "X509", "authorityID": "3c2e1115-49b7-4657-9b0f-301d7111bc6e", "fields": { "commonName": { "static": "sql.example.internal" } } },
"key": { "type": "ECDSA_P256", "protection": "NONE", "store": "MACHINE" },
"policy": { "operatingSystem": ["Windows"] },
"files": {
"windows": {
"key": {
"path": "D:\\certs\\sql\\server.key",
"acl": {
"inherit": false,
"entries": [{ "principal": "CORP\\svc-sql", "access": "READ" }]
}
},
"certificate": { "path": "D:\\certs\\sql\\server.crt" },
"root": { "path": "D:\\certs\\sql\\root.crt" }
}
}
}
```

A principal can be a local or domain account or group, a virtual service account such as `NT SERVICE\MSSQLSERVER`, a group managed service account such as `CORP\gmsa-sql$`, or a SID string such as `S-1-5-21-…`.

By default, a Windows credential with a software key in the machine scope is also imported into the Windows key store and `LocalMachine\My`, next to its files.
Set `"windowsStore": false` on the `key` to keep it to its files alone, for software that reads the files and shouldn't find the credential in the stores.
It requires `"protection": "NONE"` and `"store": "MACHINE"`.

## Field reference

| Field | Applies to | Values | Default |
| --- | --- | --- | --- |
| `path` | key, certificate, root | Absolute path in the platform's style | The agent's directory for the credential |
| `format` | key | `DEFAULT`, `PKCS8`, `CLASSIC`, `TSS2` | `DEFAULT` |
| `contents` | certificate | `CHAIN` (leaf and intermediates) or `LEAF` | `CHAIN` |
| `owner`, `group` | Linux, macOS | A name or a numeric ID, as a string | The agent's user and group |
| `mode` | Linux, macOS | Octal string, `"0000"` to `"0777"` | `"0640"`; `"0600"` for SSH user credentials |
| `acl` | Linux, macOS | List of `{type: USER or GROUP, name, access}`, `access` a combination of `r`, `w`, `x` | No extra entries |
| `acl.inherit` | Windows | `true` keeps the access the folder grants | `false` once `acl` is set |
| `acl.entries` | Windows | List of `{principal, access}`, `access` one of `READ`, `MODIFY`, `FULL_CONTROL` | See [Windows defaults](#windows-defaults) |
| `key.windowsStore` | Windows | `false` keeps the credential out of the Windows stores | `true` |

The root CA is always written to its own file and never into the certificate file.
SSH credentials take `key` and `certificate` only.

## Rules

The API refuses a credential that breaks these rules, with an error that names the field:

- **A custom key path needs a software key.** `key.path` is accepted only with `"protection": "NONE"` and without `key.pubFile`. The agent writes the key there and nowhere else.
- **Paths are absolute** in the platform's style (`/…` on Linux and macOS, `C:\…` or `\\server\share\…` on Windows), and distinct within a block.
- **Settings stay on their platform.** `owner`, `group`, and `mode` can't appear under `windows`, and a Windows access list can't appear under `linux` or `macos`. A block for an operating system the credential's `policy.operatingSystem` excludes is refused.
- **`SYSTEM` keeps full control on Windows.** An access list that gives `SYSTEM` (`NT AUTHORITY\SYSTEM`, `S-1-5-18`) less than `FULL_CONTROL` is refused, and each principal can be listed once.

The API doesn't check owners, groups, or principals: they exist only on the device.
The agent reports a name it can't find when it writes the file.

### Windows defaults

- The key file always grants `SYSTEM` and `BUILTIN\Administrators` full control, with inheritance blocked; `acl.entries` add principals to that.
Listing the Administrators with `READ` or `MODIFY` lowers their access. They can't be removed.
- The certificate and root files inherit their folder's permissions unless they have an `acl`. With one, `SYSTEM` gets full control in addition to the listed entries.
- A virtual service account (`NT SERVICE\<name>`) resolves only once its service is installed. Until then the agent reports the principal as not found and retries; the service's SID works before the service exists.
- Domain names are resolved when the file is written, so the device needs to reach a domain controller.

## How the agent writes files

- **Atomically.** The agent writes each file to a temporary file in the same folder, with its permissions already applied, and renames it over the target. An app never reads a half-written file, and a failed write leaves the previous file in place.
- **Key first.** The key is written before the certificate, and the certificate before the root. If the key can't be written, the other files aren't either, so a new certificate never sits next to an old key.
- **Open files on Windows.** A file another program holds open without delete sharing can't be replaced. The agent retries for about two seconds, then reports the error and tries again later.
- **No folders.** The agent never creates a folder for a custom path. Create it with the ownership and permissions your app expects.
- **Old files stay.** When a path changes, the agent writes the new path and leaves the file at the old one, including an old key. Delete it yourself.
- **No restart.** Writing the files doesn't restart the app. Use the workload's reload settings or hooks for that.

If a file can't be written as configured, the agent reports it on the credential's status for that device, and on Windows in the event log.
See [Credential files not written](./troubleshooting-agent.mdx#credential-files-not-written).

## Compatibility

- **API versions.** The per-platform `files` object is available in API version `2026-11-01` and later. Earlier versions keep the flat `files` object (`crtFile`, `keyFile`, `rootFile`, `uid`, `gid`, `mode`), which applies the same settings on every device; `2026-11-01` refuses it.
Earlier versions show a credential's per-platform settings in the flat shape when they can be expressed that way, and omit them otherwise. They refuse an update that would drop them.
- **Workloads on `2023-11-01`.** A workload's `certificateInfo.files` takes the same per-platform blocks. It can't be combined with the flat fields, and a save without `files` that changes the flat fields is refused with `412`. Send `"files": {}` to go back to the flat fields.
- **Agents.** Agents older than 0.72.0 apply the paths, the key format, and the key file's owner, group, mode, and ACL entries to all three files. They ignore everything else: per-file permissions, `contents`, Windows access lists, and `windowsStore`.
28 changes: 27 additions & 1 deletion platform/troubleshooting-agent.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
updated_at: September 8, 2026
updated_at: October 23, 2026
title: Troubleshooting Guide
html_title: Smallstep Troubleshooting Guide
description: Troubleshoot Smallstep Device Identity issues. Diagnose platform, MDM, and endpoint problems with step-by-step guidance.
Expand Down Expand Up @@ -543,6 +543,32 @@ If you ran `modutil -add` before exporting `P11_KIT_SERVER_ADDRESS` (see [PKCS#1
- **If a certificate exists**: The issue is between the Smallstep Agent and the client application. Check the client's certificate configuration. See [Platform-Specific Reference](#platform-specific-reference) for certificate locations.
- **If no certificate exists**: The resource may not be assigned to this device, or there may be an agent issue. Check the device's **Resources** tab and run `doctor`.

#### Credential files not written

**Symptoms:**
- A credential with [custom files](./credential-files.mdx) shows an error or warning status on the device
- An app can't find or read its key or certificate
- Event ID 801 from `SmallstepAgent` in the Windows Application log

**Troubleshooting steps:**
1. Run `step-agent status` on the device. Under a credential that reported an error or warning, a `last report:` line gives the file, its path, and the reason.
2. On Windows, read the agent's file events:
```powershell
Get-WinEvent -FilterHashtable @{ LogName = 'Application'; ProviderName = 'SmallstepAgent'; ID = 800, 801 }
```
Event 801 (Error) carries `file` (`key`, `certificate`, or `root`), `path`, `fault`, `principal`, and `error`. Event 800 (Information) means the files were written as configured again.
3. Fix the cause the `fault` names:

| `fault` | Cause | Fix |
| --- | --- | --- |
| `directory-missing` | The folder of a custom path doesn't exist. The agent never creates it. | Create the folder with the ownership and permissions the app expects. |
| `principal-not-found` | An owner, group, or Windows principal doesn't exist on the device. A domain account also fails while no domain controller is reachable. | Create the account, check its spelling, or use its numeric ID or SID. An `NT SERVICE\<name>` account exists only once its service is installed. |
| `acl-unsupported` | The file system can't hold the configured ACL entries. | Move the file to a file system with ACL support, or remove the `acl` entries. |
| `write-failed` | Anything else, such as a file another program holds open on Windows, or no space left. | Read `error` for the reason. |

The agent writes a credential's files again at its next issuance, renewal, or restart, so a fix made on the device alone is picked up then. To retry right away, run `step-agent doctor --renew`.
Until a write succeeds, the previous files stay in place, and the agent never writes a file with broader permissions than configured.

---

## Getting help
Expand Down
Loading