Skip to content

Credentials API

This is the method-by-method API reference for the credentials subsystem. For a task-oriented walkthrough (enabling the vault, reading and writing secrets, KeyProviders, central sync), see the Credentials guide. Every field, default, and validation rule for the credentials config section lives in the config schema reference.

The subsystem is opt-in: the accessor returns an “absent” value unless a credentials config section is present (and, in Rust, unless the credentials cargo feature is also built). Keys are transparently namespaced by {ThingName}/{ComponentName}.

Get the service from the SDK handle. The name and the absent value differ per language.

Language Method Returns when configured Returns when not configured
Java getCredentials() CredentialService null
Python get_credentials() CredentialService None
Rust credentials() Some(Arc<dyn CredentialService>) None
TypeScript credentials() CredentialService undefined

The platform-profile default KEK provider (env on KUBERNETES) is applied at init but never auto-enables credentials — only a credentials config section does.

Most components get the service from the accessor above. The factory is for standalone use (no SDK handle) or for building a namespaced service yourself. It builds a DefaultCredentialService from the parsed credentials config.

// final class com.mbreissi.ggcommons.credentials.Credentials (static methods)
static CredentialService open(JsonObject credentialsConfig);
static CredentialService open(JsonObject credentialsConfig, String namespace);
static CredentialService open(JsonObject credentialsConfig, String namespace,
String defaultKeyProviderType);
// also exposes the KeyProvider builder used internally:
static KeyProvider buildKeyProvider(JsonObject kp, String defaultKeyPath);
static KeyProvider buildKeyProvider(JsonObject kp, String defaultKeyPath, String defaultType);

The public seam every component depends on. Every read first picks up cross-process vault changes (reload-if-changed) before returning.

Capability Description
get Latest version as a Secret, or absent.
getVersion A specific version by id, or absent.
exists Whether the secret exists (latest version).
list SecretMeta for all secrets whose name starts with a prefix ("" = all).
versions The version-id history for one secret.
put Store bytes, return the new version id. Keeps the last keepVersions (default 2).
delete Remove all versions; returns whether anything was removed.
refresh Force an immediate central pull (no-op without central sync).
stats Non-sensitive CredentialStats for the metrics bridge.
getBytes / getString / getJson Decode the latest value straight to bytes / UTF-8 / parsed JSON.
typed views getAwsCredentials / getBasicAuth / getTlsBundle / getKafkaSasl — see Typed views.

The per-language signatures follow.

public interface CredentialService {
Optional<Secret> get(String name);
Optional<Secret> getVersion(String name, String version);
boolean exists(String name);
List<SecretMeta> list(String prefix);
List<String> versions(String name);
String put(String name, byte[] value, PutOptions opts);
boolean delete(String name);
default String put(String name, byte[] value); // PutOptions defaulted
default void refresh();
default CredentialStats stats();
default Optional<byte[]> getBytes(String name);
default Optional<String> getString(String name);
default Optional<JsonElement> getJson(String name);
default String putString(String name, String value); // Java only
default Optional<AwsCredentials> getAwsCredentials(String name);
default Optional<BasicAuth> getBasicAuth(String name);
default Optional<TlsBundle> getTlsBundle(String name);
default Optional<KafkaSasl> getKafkaSasl(String name);
}

The decrypted value plus its metadata. The bytes accessor returns the raw value; asString / as_str decodes strict UTF-8; asJson / as_json parses JSON. The default string form redacts.

public final class Secret {
String name();
String version();
long createdMs();
String source();
String contentType();
Map<String, String> labels();
byte[] bytes();
String asString(); // strict UTF-8
JsonElement asJson();
// toString() -> Secret{name=.., version=.., bytes=<N redacted>}
}

Optional metadata for put. Fields: ttlSecs / ttl_secs, labels, contentType / content_type, source, centralVersionId / central_version_id.

// mutable fluent class; setters for ttlSecs/labels/contentType, public source/centralVersionId fields
PutOptions opts = new PutOptions().ttlSecs(3600).contentType("text/plain");
String version = creds.put("api/token", token, opts);

Both are value-free metadata. SecretMeta is what list and versions surface; CredentialStats feeds the metrics bridge. Field names are camelCase (Java/TypeScript) or snake_case (Python/Rust), but the shapes are identical.

  • SecretMetaname, version, createdMs / created_ms, ttlSecs / ttl_secs (optional), source, labels.
  • CredentialStatssecretCount / secret_count, lastSyncAgeMs / last_sync_age_ms (optional; null / None when there is no central sync), syncFailures / sync_failures, rotations.

Four convenience getters parse a secret whose value is canonical JSON into a typed object. They are present-or-absent for a missing secret and error on the wrong shape: a CredentialException (Java) / CredentialError (Python, TypeScript), or a serde deserialization error in Rust.

The on-disk JSON keys are identical across all four languages:

View Canonical JSON keys
AWS credentials accessKeyId, secretAccessKey, sessionToken?, expiry?
Basic auth username, password
TLS bundle certPem, keyPem, caPem?
Kafka SASL mechanism? (default PLAIN), username, password

The constructed object’s field casing differs: camelCase (Java records / TypeScript interfaces) vs snake_case (Python dataclasses / Rust structs — e.g. access_key_id, cert_pem).

The vault wraps each secret’s data-encryption key (DEK) under a key-encryption key (KEK) held by a KeyProvider, selected by vault.keyProvider.type. The effective type is explicit type ▸ platform default ▸ "file"; the platform default is env on KUBERNETES and file everywhere else.

type KEK custodian Key fields
file (default) A local 32-byte keyfile keyPath
env A base64 32-byte KEK from an env var / mounted Secret envVar (default GGCOMMONS_VAULT_KEK)
kms / greengrass (aliases) AWS KMS via TES kmsKeyId, region, endpointUrl
pkcs11 HSM / TPM (wrap stays inside the token) modulePath, tokenLabel, keyLabel, pinEnv

The env provider reads a base64-encoded 32-byte KEK from the named variable (default GGCOMMONS_VAULT_KEK), tolerates a trailing newline, and errors on unset / empty / bad-base64 / wrong-length. It is crypto-identical to file given the same raw key.

The default env-var name is exposed as a constant.

Language Constant Value
Java EnvKeyProvider.DEFAULT_ENV_VAR GGCOMMONS_VAULT_KEK
Python EnvKeyProvider.DEFAULT_ENV_VAR GGCOMMONS_VAULT_KEK
Rust keyprovider::DEFAULT_KEK_ENV_VAR GGCOMMONS_VAULT_KEK
TypeScript (literal default in config.ts) GGCOMMONS_VAULT_KEK

Rust has a double gate: the service needs both the credentials cargo feature and a credentials config section. The file and env providers are always available with the credentials feature; kms / greengrass additionally require credentials-aws, and pkcs11 requires credentials-pkcs11. Building without the matching feature is a configuration error at open time. Java, Python, and TypeScript have no compile-time gate — their optional KMS / PKCS#11 dependencies load lazily at runtime.

Custom KEK custodians implement the KeyProvider interface (a narrow wrap/unwrap-DEK seam). The custodian id is written to the vault’s KekInfo.provider; the KEK never lands on disk in plaintext.

public interface KeyProvider {
String providerId(); // e.g. "file"
KekInfo wrapDek(String vaultId, byte[] dek);
byte[] unwrapDek(String vaultId, KekInfo kek);
}

Set credentials.central.type to seed and refresh the local vault from a central source.

  • central.type is one of none (the default — local-only vault) or awsSecretsManager. An unknown value is rejected at open time in every language.
  • When awsSecretsManager, a sync engine pulls central.sync.secrets on bootstrapOnStart (default true) and every refreshIntervalSecs (default 300).
  • Each sync.secrets entry is a bare name (whose central id is the device-namespaced path) or { "name": ..., "from": ... } to pull from an explicit or fleet-shared central id.
  • refresh() forces an immediate pull (a no-op when central sync is not configured).

In Rust the central source requires the credentials-aws feature; configuring awsSecretsManager without it returns an error at open time. Java, Python, and TypeScript load the AWS SDK lazily.

The Rust config types are CentralConfig, SyncSelect, and SyncEntry (all serde camelCase); Java/Python/TypeScript read the same fields from the parsed config object.

Every get / getVersion / put / delete is recorded through a pluggable AuditSink — the operation, secret name, version, source, and outcome, but never the value. Auditing is on by default; disable it with credentials.audit.enabled = false.

The audit event carries metadata only:

  • op"get" | "put" | "delete"
  • name — caller-facing secret name (transparent namespace stripped)
  • version — version touched, or "-" when not applicable / not found
  • source"local" | "central" | "-"
  • outcome"hit" | "miss" | "ok"

The default LogAuditSink writes to a dedicated per-language logger so you can route or filter it independently:

Language Types Audit logger / target
Java AuditSink, AuditEvent, LogAuditSink com.mbreissi.ggcommons.credentials.audit
Python (audit module) ggcommons.credentials.audit
Rust (audit module) ggcommons::credentials::audit (tracing target)
TypeScript LogAuditSink, logSink the LogAuditSink logger

In Java the sink is a one-method interface (void record(AuditEvent event)) and AuditEvent is a record; only the target/logger name differs across languages.

Resolves $secret references inside a config element so messaging / streaming config can reference vault secrets without embedding plaintext. Use {"$secret":"name"} to substitute the secret’s whole value as a string, or {"$secret":"name","field":"k"} to substitute field k of the secret’s JSON. Resolution errors if the secret (or field) is absent.

// returns a deep COPY; the input is never mutated
JsonElement resolved = SecretRefs.resolve(JsonElement element, CredentialService creds);

A minimal read, guarding for the absent service first. (Verified against each SDK’s credentials test suite and dev guide.)

CredentialService creds = gg.getCredentials();
if (creds == null) return; // no `credentials` section
creds.put("db/password", "s3cr3t".getBytes(StandardCharsets.UTF_8)); // put(name, byte[])
Optional<Secret> s = creds.get("db/password");
s.ifPresent(v -> LOGGER.info("ok bytes={} source={}", v.bytes().length, v.source()));
Optional<BasicAuth> ba = creds.getBasicAuth("svc/login"); // typed view (throws on wrong shape)
  • Credentials guide — enabling the vault, KeyProviders, central sync, and the security model, with task-oriented examples.
  • Config schema reference — every credentials field, default, and validation rule.
  • Parameters API — offline-first externalized config that reuses the credentials vault as an encrypted cache.