Class SecretsStore
- All Implemented Interfaces:
ISecretsStore, AutoCloseable
SECRETS-STORE-DESIGN.md sections 4 to 7, build order SK-2): one owner-only XML property file, <user.home>/.iizi-secrets/iizi-secrets.xml unless the system property "iizi.secrets.file" names another (decision P-12 (a); there is no environment variable), that keeps the values of the secret-marked properties of every file on the machine, keyed by context and atom path (ISecretsContext).The file is a property container loaded and saved through the caller's PropFactory, like every IIZI settings file: the root "secrets", a container per context, containers along the atom path, and each leaf of the class of the original property with that class's own codec (P-13): a PasswordProp stays obfuscated, a StringProp is clear, a PKCS12IdentityProp is its bytes. The obfuscation protects against a glance only; the protection of the file is its owner-only mode (section 6.2).
Changes - put(String,String), putProp(String,GProp), remove(String) - are held in memory as operations and written by flush(), which runs about 10000L ms after the last change on the store's own timer engine (never the system engine, which the server disposes before this store), at dispose(), and from the store's own JVM shutdown hook when dispose never ran (P-5 (a)). A flush (section 6.3) takes an exclusive lock on the owner-only sibling ".lock" - a FileLock between processes, a ReentrantLock per file between the stores of one JVM, which a FileLock cannot separate - re-reads the file, which another writer may have changed, applies this store's operations to that fresh image (last writer wins per key, keys this store did not touch are kept), writes an owner-only temporary sibling and forces it, keeps the current file as ".previous", and moves the temporary file over the store atomically. A kill at any moment leaves the old file or the new one. A failed flush keeps its operations, logs one SEVERE and is retried at the next change or at dispose.
External changes (P-9 (a)): a PushFileWatcher watches the file; on a change the store re-reads it under the lock and tells its listeners which keys changed.
Permissions (section 6.2, P-8): the directory is created 0700 and every file 0600 from its first byte. A directory writable by group or others is refused. A directory or file found broader is narrowed with a WARNING. A file that cannot be written - a mounted container secret - makes a read-only store, reported once at INFO and never written, and is refused when it is readable by group or others. On Windows an ACL that lets a broad principal read is a WARNING once per load.
Nothing leaks. No log line, exception text or toString carries a value. Keys and paths are not secret and are named.
Thread-safe.
- Author:
- Christopher Mindus
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumThe class of an entry, which the file keeps (P-13).Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe directory of the default store belowuser.home: ".iizi-secrets".static final StringThe name of the default store file: "iizi-secrets.xml".static final longThe delay between the last change and the flush, in milliseconds: 10000L.static final StringThe suffix of the lock file beside the store: ".lock".static final intThe longest context name: 128.static final StringThe suffix of the previous copy beside the store: ".previous".static final StringThe system property naming the store file: "iizi.secrets.file".static final StringThe name of the root container of the file: "secrets".static final StringThe suffix of a temporary file beside the store: ".tmp".Method Summary
Modifier and TypeMethodDescriptionvoidaddChangeListener(Consumer<Set<String>> listener) Adds a listener told, on the watcher's thread or the thread ofreload(), the keys whose value another process changed and this store has no pending change of (P-9 (a)): what a loaded secret property may take quietly.static voidChecks a key: a context of 1 to 128 characters ofa-z A-Z 0-9 . _ -, then at least one atom name, separated byISecretsContext.KEY_SEPARATOR.voidclose()Same asdispose().static PathGets the store file of this machine: the system property "iizi.secrets.file" when it is a path, else the default belowuser.home.voiddispose()Disposes of the store: flushes pending changes, stops the timer and the watcher, and removes the shutdown hook.booleanflush()Writes the pending changes now (section 6.3).Gets the value of a key: the pending change of this store, else the file's.getFile()Gets the store file.Gets the kind of a key.booleanTells whether the store has an entry for a key, pending changes included, throughkeys(): the value is not read (ruling PP-D-4).booleanTells whether changes are waiting for a flush.booleanTells whether the store is disposed of.booleanTells whether the store is read-only: a file or directory that cannot be written.keys()Gets the keys, pending changes included.static SecretsStoreopen(PropFactory factory) Opens the store of this machine: the file named by the system property "iizi.secrets.file", else<user.home>/".iizi-secrets"/"iizi-secrets.xml", with the flush delay of 10000L ms and a watcher on the file.static SecretsStoreopen(Path file, PropFactory factory) Opens a store file with the flush delay of 10000L ms and a watcher on the file.static SecretsStoreopen(Path file, PropFactory factory, long flushDelay, boolean watch) Opens a store file.voidPuts a value, keeping the kind of an existing entry; a new entry is aSecretsStore.Kind.STRING.voidput(String key, String value, SecretsStore.Kind kind) Puts a value of a kind under a key.voidPuts the value of a property under a key, with its kind: the file keeps the class (P-13).reload()Re-reads the file when another process changed it, and tells the listeners which keys changed.voidRemoves a key.booleanremoveChangeListener(Consumer<Set<String>> listener) Removes a listener.toString()Names the file, the entry count and whether the store is read-only; never a key's value.
Field Details
PROPERTY
The system property naming the store file: "iizi.secrets.file". A path, never a value.- See Also:
DEFAULT_DIRECTORY
The directory of the default store belowuser.home: ".iizi-secrets".- See Also:
DEFAULT_NAME
The name of the default store file: "iizi-secrets.xml". It keeps "secret" in its name on purpose (design section 6.1): that is what the Claude Code guards of the source tree match.- See Also:
LOCK_SUFFIX
The suffix of the lock file beside the store: ".lock".- See Also:
PREVIOUS_SUFFIX
The suffix of the previous copy beside the store: ".previous".- See Also:
TEMP_SUFFIX
The suffix of a temporary file beside the store: ".tmp".- See Also:
FLUSH_DELAY_MILLIS
public static final long FLUSH_DELAY_MILLISThe delay between the last change and the flush, in milliseconds: 10000L.- See Also:
ROOT_NAME
MAX_CONTEXT_LENGTH
public static final int MAX_CONTEXT_LENGTHThe longest context name: 128.- See Also:
Method Details
open
Opens the store of this machine: the file named by the system property "iizi.secrets.file", else<user.home>/".iizi-secrets"/"iizi-secrets.xml", with the flush delay of 10000L ms and a watcher on the file.- Parameters:
factory- The factory that loads and saves the file: the server's, or the Designer's.- Returns:
- The store.
- Throws:
IOException- If the file or its directory is refused, or the file cannot be read. The caller reports it and runs without a store: a missing secret never stops a server (P-15).
open
Opens a store file with the flush delay of 10000L ms and a watcher on the file.- Parameters:
file- The store file; its directory is created when missing.factory- The factory that loads and saves the file.- Returns:
- The store.
- Throws:
IOException- If the file or its directory is refused, or the file cannot be read.
open
public static SecretsStore open(Path file, PropFactory factory, long flushDelay, boolean watch) throws IOException Opens a store file.- Parameters:
file- The store file; its directory is created when missing.factory- The factory that loads and saves the file.flushDelay- The flush delay in milliseconds; zero or less writes at every change.watch- true to watch the file for changes by other processes.- Returns:
- The store.
- Throws:
IOException- If the file or its directory is refused, or the file cannot be read.
defaultFile
Gets the store file of this machine: the system property "iizi.secrets.file" when it is a path, else the default belowuser.home.- Returns:
- The file, absolute.
get
Gets the value of a key: the pending change of this store, else the file's.- Specified by:
getin interfaceISecretsStore- Parameters:
key- The key.- Returns:
- The element-string value, or null when there is no entry.
has
Tells whether the store has an entry for a key, pending changes included, throughkeys(): the value is not read (ruling PP-D-4).- Specified by:
hasin interfaceISecretsStore- Parameters:
key- The key.- Returns:
- true when there is an entry.
put
Puts a value, keeping the kind of an existing entry; a new entry is aSecretsStore.Kind.STRING. UseputProp(String,GProp)orput(String,String,Kind)to name the kind.- Specified by:
putin interfaceISecretsStore- Parameters:
key- The key.value- The element-string value.- Throws:
IllegalArgumentException- If the key is not valid, or the value is not one of its kind.IllegalStateException- If the store is read-only or disposed of.NullPointerException- If the key or the value is null.
putProp
Puts the value of a property under a key, with its kind: the file keeps the class (P-13).- Specified by:
putPropin interfaceISecretsStore- Parameters:
key- The key.prop- The property, of a secret-capable class, with a value.- Throws:
IllegalArgumentException- If the key is not valid, the class is not secret-capable or the property has no value.IllegalStateException- If the store is read-only or disposed of.
put
Puts a value of a kind under a key.- Parameters:
key- The key.value- The element-string value of that kind.kind- The kind.- Throws:
IllegalArgumentException- If the key is not valid, or the value is not one of the kind.IllegalStateException- If the store is read-only or disposed of.NullPointerException- If an argument is null.
remove
Removes a key. Removing an absent key does nothing.- Specified by:
removein interfaceISecretsStore- Parameters:
key- The key.- Throws:
IllegalArgumentException- If the key is not valid.IllegalStateException- If the store is read-only or disposed of.
isReadOnly
public boolean isReadOnly()Tells whether the store is read-only: a file or directory that cannot be written.- Specified by:
isReadOnlyin interfaceISecretsStore- Returns:
- true when read-only.
getFile
keys
getKind
Gets the kind of a key.- Parameters:
key- The key.- Returns:
- The kind, or null when there is no entry.
hasPendingChanges
public boolean hasPendingChanges()Tells whether changes are waiting for a flush.- Returns:
- true when a change is not written yet.
addChangeListener
Adds a listener told, on the watcher's thread or the thread ofreload(), the keys whose value another process changed and this store has no pending change of (P-9 (a)): what a loaded secret property may take quietly.- Parameters:
listener- The listener.
removeChangeListener
flush
public boolean flush()Writes the pending changes now (section 6.3). Nothing is written when nothing is pending or the store is read-only.- Returns:
- true when nothing is pending afterwards but what was changed meanwhile, false when the store is read-only with changes, or the flush failed (logged SEVERE, the changes kept for the next flush).
reload
Re-reads the file when another process changed it, and tells the listeners which keys changed. The watcher calls it; so may the lifecycle.- Returns:
- The keys whose value changed and are not pending in this store; empty when the file did not change.
- Throws:
IOException- If the file cannot be read.
dispose
public void dispose()Disposes of the store: flushes pending changes, stops the timer and the watcher, and removes the shutdown hook. Idempotent. Getting still works afterwards; changing does not.isDisposed
close
toString
checkKey
Checks a key: a context of 1 to 128 characters ofa-z A-Z 0-9 . _ -, then at least one atom name, separated byISecretsContext.KEY_SEPARATOR.- Parameters:
key- The key.- Throws:
IllegalArgumentException- If the key is not valid.NullPointerException- If the key is null.