A service needs credentials such as a reporting API token without putting them
in source code or its container image. A secret store holds those private
values outside the application; secret.Store reads them through your chosen
driver. Provider permissions, retention, and auditing remain part
of that provider’s configuration.
Reading a secret produces an ordinary Go value. The facade does not cache it, refresh a running client, or revoke the credential in the system that accepts it. Rotation coordinates those separate changes.
Reading Values
With a configured *secret.Store named secrets and an operation context ctx,
read a provisioned text credential:
token, err := secrets.String(ctx, "reporting-token")
On success, token contains the current value. String converts bytes to a Go
string without validating UTF-8. For binary material, use Raw instead; its
returned byte slice belongs to you. Azure accepts only UTF-8 text and Vault
stores JSON objects, so choose the payload format
before provisioning.
A missing name or requested version wraps secret.ErrNotFound. Permission,
authentication, and connectivity failures are not missing-secret signals.
Use errors.Is for portable error checks rather than matching provider text.
An already-canceled context fails with its context error without changing storage.
Focused examples keep error results visible but omit routine propagation. Handle errors before using results; the startup example below retains the check that prevents using a missing credential.
Typed Credentials
When one credential contains related fields, store a JSON object and share a
definition that binds its provider name to a Go type. Import secret from
arkoren.dev/core/service/secret:
type ReportingCredentials struct {
Account string `json:"account"`
Token string `json:"token"`
}
var reportingSecret = secret.NewSecret[ReportingCredentials]("reporting-client")
The definition performs no I/O and creates no provider resource. After provisioning that JSON secret, read it through the facade:
credentials, err := secrets.Get(ctx, reportingSecret)
On success, credentials has type ReportingCredentials. Validate required
fields before using them: a shared definition is not a provider schema, and
independent definitions can assign different types to the same name.
Definitions are immutable and safe to share. An empty name or zero definition
reports secret.ErrInvalidSecret; Err() checks this without a read. Get
rejects invalid definitions before provider I/O, with an already-canceled context
taking precedence. JSON decoding errors can include the name and underlying
details; neither decoded values nor returned errors are redacted.
Shared Names
For several JSON credentials of the same type, a namespace composes their names. Choose a separator valid for your provider rather than assuming slash paths work everywhere:
reports := secret.NewNamespace[ReportingCredentials]("reports", "-")
archiveSecret := reports.Secret("archive")
credentials, err := secrets.Get(ctx, archiveSecret)
This reads the literal name reports-archive; it does not create a namespace in
the provider or grant access. Azure needs flat names. Google accepts a full
resource path or an unqualified ID with a configured project; slashes in a full
resource path are not valid separators inside that ID. AWS and Vault permit
path-style conventions. See Drivers before choosing deployed names.
Derive publishes an independent variant. Subnamespace joins a child prefix
with the parent’s separator before applying settings for future joins:
regional := reports.Subnamespace("eu")
replicaSecret := regional.Secret("replica")
The resulting name is reports-eu-replica. No escaping or normalization occurs,
and an empty separator concatenates components. Validate or unambiguously encode
untrusted components to prevent collisions. Namespaces are not authorization or
isolation boundaries.
Empty prefixes, empty child names, and zero namespaces are invalid; descendants
retain the first declaration error. Name, Separator, and Err inspect a
namespace. Construction callbacks run once in order, including on invalid
declarations; nil callbacks panic, and retained drafts cannot change published
values.
Startup Loading
If a client uses its credential only during construction, load and validate the
credential before starting that client. A required-secret failure should stop
startup rather than silently select an empty value or generate a replacement.
For a safe application-owned error, import the standard-library errors package:
token, err := secrets.String(ctx, "reporting-token")
if err != nil {
return errors.New("reporting credential could not be loaded")
}
if token == "" {
return errors.New("reporting credential is empty")
}
Only the success path passes token to the reporting client. The generic error
avoids exposing the provider error or secret name through ordinary startup logs;
use a protected diagnostic path if you need the underlying cause. A successful
read checks storage access, not whether the reporting API accepts that token.
Use environment settings for nonsecret choices such as the deployment’s project or vault address. Grant each runtime identity access only to the secrets it needs. Keeping store reads out of request handling removes those reads from the request path, but a store outage can still prevent startup or refresh.
Private Values
Treat secret names as potentially private as well as their contents. Typed
definitions and namespaces redact identities under ordinary formatting and
structured logging, but Name() deliberately exposes them. Raw Reference and
Value records, strings, decoded structs, native objects, and returned errors do
not inherit that protection.
Do not log credentials or credential-bearing connection strings, include them in public URLs, or return them in API responses. Limit which application objects retain them. Driver diagnostic records mask selected fields only; consult the provider logging rules, particularly when supplying native clients.
Drivers copy input and returned bytes. Clearing your returned slice does not
clear storage, other copies, strings, or decoded fields; copying a secret.Value
record alone still shares its Data slice. Arkoren does not guarantee secret
zeroization. Deleting a secret does not erase values already held by consumers.
For all definitions and operations, see the
secret API reference.