Static CredStore Plugin
CredStore storage-backend plugin that serves pre-configured secrets from YAML configuration. Designed for development, testing, and fixed-credential deployments where a full secrets vault is unnecessary.
Overview
The cf-static-credstore-plugin module provides:
- Static secret mapping — secrets defined in YAML config, loaded and validated at init
- Four sharing scopes — Private, Tenant, Shared (tenant-scoped), and Global
- O(1) lookup — secrets are pre-indexed into separate
HashMaps per scope - Deterministic precedence — lookup order: Private → Tenant → Shared → Global
- Strict config validation — invalid keys, duplicate entries, and contradictory field combinations are rejected at startup
The plugin registers itself via the types registry as a CredStorePluginClientV1 implementation and is discovered by the credstore gateway module.
Configuration
Add the plugin section under your module configuration:
static-credstore-plugin:
config:
vendor: "hyperspot" # GTS vendor name (default: "hyperspot")
priority: 100 # Plugin priority, lower = higher (default: 100)
secrets:
# Private secret — only accessible by this specific user in this tenant
- tenant_id: "11111111-1111-1111-1111-111111111111"
owner_id: "22222222-2222-2222-2222-222222222222"
key: "my-api-key"
value: "sk-secret-123"
# Tenant secret — accessible by any user within the tenant
- tenant_id: "11111111-1111-1111-1111-111111111111"
key: "team-api-key"
value: "sk-team-456"
# Shared secret — tenant-scoped, visible to descendant tenants via gateway walk-up
- tenant_id: "11111111-1111-1111-1111-111111111111"
key: "org-api-key"
value: "sk-org-789"
sharing: "shared"
# Global secret — accessible by any tenant and any user (fallback)
- key: "platform-api-key"
value: "sk-global-000"
Secret fields
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id |
UUID |
No | Tenant scope. None → global secret. |
owner_id |
UUID |
No | Subject scope. Only valid for private sharing. Requires tenant_id. |
key |
string |
Yes | Secret reference key. Must match SecretRef format (alphanumeric, -, _). |
value |
string |
Yes | Plaintext secret value (converted to bytes at init). |
sharing |
SharingMode |
No | Explicit sharing mode. When omitted, inferred from tenant_id/owner_id. |
Sharing mode inference
When sharing is omitted, the mode is inferred automatically:
tenant_id |
owner_id |
Inferred mode |
|---|---|---|
None |
— | shared (global) |
Some |
None |
tenant |
Some |
Some |
private |
You can override the default with an explicit sharing value (e.g. set sharing: "shared" on a tenant-scoped secret to make it visible to descendant tenants).
Validation rules
The plugin rejects invalid configurations at startup with a descriptive error:
- Invalid key —
keymust be a validSecretRef(alphanumeric,-,_) - Nil UUIDs —
tenant_idandowner_idmust not be00000000-0000-0000-0000-000000000000 owner_idwithouttenant_id— global secrets cannot have an ownerowner_idon non-Private secret —owner_idis only valid when resolved sharing isprivateprivatewithoutowner_id— explicitsharing: "private"requiresowner_id- Global with non-Shared mode —
tenant_id: Noneonly allowsshared(or inferredshared) - Duplicate keys — within the same scope (same tenant + sharing mode), keys must be unique
Lookup precedence
When a secret is requested, the plugin checks maps in this order:
- Private — keyed by
(tenant_id, owner_id, key), matched againstSecurityContext - Tenant — keyed by
(tenant_id, key), any subject in the tenant - Shared — keyed by
(tenant_id, key), tenant-scoped but visible to descendants - Global — keyed by
keyonly, fallback for any caller
The first match wins. This means a Private secret shadows a Tenant secret with the same key for the matching user, while other users in the same tenant still see the Tenant-level value.
SecretMetadata::owner_id resolution
For Private secrets, owner_id comes from the config. For Tenant, Shared, and Global secrets, owner_id is not stored — the plugin fills it from SecurityContext::subject_id() of the caller at lookup time.
Architecture
module.rs ModKit module — init, config loading, GTS registration
config.rs YAML config model + resolve_sharing() + validation docs
domain/
service.rs Service — from_config() builder + get() lookup
client.rs CredStorePluginClientV1 impl (maps SecretEntry → SecretMetadata)
mod.rs Re-exports
Init sequence
- Load
StaticCredStorePluginConfigfrom module config Service::from_config()— validate all entries, build lookup maps- Register GTS plugin instance in types-registry
- Store
Arc<Service>in module state - Register
CredStorePluginClientV1scoped client inClientHub
Testing
The test suite covers:
- Lookup per scope (private, tenant, shared, global)
- Precedence across all four scopes
- Owner/tenant isolation
- Config validation (all rejection rules)
- Sharing mode inference and explicit overrides
SecretMetadataowner resolution fromSecurityContext
License
Apache-2.0