enhancement
major
minor
defect
critical
major
minor
Context
The React interface administration displays all secrets referenced by the configurations of running services under System → Secrets, along with their availability status (#29948). So far, a missing secret can only be detected there; it must be provided outside the application (environment variable, file, vault).
The DirectorySecretStore reads each secret from the file of the same name in a directory. In a container, these are the mounted secret files (Kubernetes, Docker), which are read-only. In a simple installation (WAR in Tomcat, Debian package, developer machine with ~/.top-logic/secrets), however, the directory is writable by the application. The interface for writing secrets (WritableSecretStore, SecretStoreService#getWritableStore()) already existed, but only DatabaseSecretStore was writable, and no interface utilized it.
According to this ticket, an administrator can now set, replace, or remove a named secret directly in the overview, provided the installation’s storage allows it. The value of a secret is still never displayed.
Behavior
- Writable directory storage: DirectorySecretStore is writable as long as the application is permitted to write to the directory (checked on every request). For a single secret, the file determines this: if it exists, it must be writable; otherwise, the directory must be writable. A secret file mounted as read-only in a container is therefore never overwritten. A secret is written atomically (temporary file, then renamed), with read and write permissions granted only to the owner, where the file system supports POSIX permissions. A missing directory is not created.
- Overview of secrets: For the selected row, “Set Secret” and “Remove Secret” offer the options to write or delete the secret, respectively. The new value is entered into a password field in a dialog box. “Set Secret” is only active if the secret is accessed by its name and writing to it actually takes effect: The store is writable, there are no exceptions (overrides) for the name, and no store in a chain (ChainSecretStore) preceding the writable one already holds the secret (not even due to a read error). “Remove Secret” additionally requires that the writable store hold the secret; a secret from a store behind it (e.g., a value in the configuration) can be overwritten but not removed. A disabled command states the reason. After writing, the overview is rechecked, and the row remains selected. Since secrets are read every time they are used, the change takes effect without a restart—unlike when the service reads the secret only at startup.
- This applies to every writable store, including ` DatabaseSecretStore`, which is used to centrally manage secrets even in a multi-node installation.
Technical Notes
- WritableSecretStore#canStore(name) (default: true) returns whether a secret with this name can be written and will then be returned under that name. DirectorySecretStore returns a writable view via ` getWritableStore()` only if the directory is writable and returns `true` for ` canStore ` based on the permissions of the file or directory.
- The writable view of ChainSecretStore is determined for each request and returns false for canStore if a secret with that name is already held by a preceding store in the chain.
- SecretStoreService#canStore(name) / canRemove(name) / store(name, secret) / remove(name) (and the static variants canStoreSecret, canRemoveSecret, storeSecret, removeSecret) check for exceptions, writable storage, and canStore, and report an invalid write operation with the reason.
- SecretReference#getSecretName() returns the name of a secret retrieved by its name.
- The overview uses the TL-Script functions secretStorable, secretRemovable, secretStore, and secretRemove (SecretFunctions) and the dialog admin/system/set-secret.view.xml; the value is only passed to secretStore and is neither logged nor returned.
- The developer article credentials/secret-stores describes how to write secrets.