AnnouncementsIntroducing Arkoren

    Menu

    Secrets Drivers

    Choose and configure a secret provider with explicit payload, permission, retention, and lifecycle boundaries.

    What's on this page
    1. Compare five drivers by payload format, shared storage, version selection, deletion, and native access.
    2. Configure provider identities and clients with explicit startup, cleanup, and logging ownership.
    3. Check disposable AWS and Vault fixtures without treating emulator success as production access verification.
    On this page

    Use memory for fixture credentials in tests and local development. For a deployed service, choose the cloud store your organization operates, or Vault when you already manage its KV v2 engine and authentication. Configure the workload’s identity and least-privilege policy explicitly: a default credential chain can find credentials, but it does not prove they are the intended identity or grant secret access.

    The examples assume your application module already depends on arkoren.dev/core. ctx is a startup or operation context. Check construction errors before wrapping a driver; secret.New neither validates nor starts it. Use a successful credential read to check access, then separately verify the credential with the system that accepts it.

    Compatibility Matrix

    This comparison covers Arkoren’s secret contract, not the provider’s whole administration API. Versioned reads select a retained concrete version within one logical secret; they do not reserve its name or retain history.

    Driver Versioned Reads Arbitrary Bytes Shared Storage Permanent Delete Native Access Observed Mutations
    Memory Yes Yes No Yes No Yes
    AWS Secrets Manager Yes Yes Yes Yes Yes
    Azure Key Vault Yes Yes Yes Yes
    Google Cloud Secret Manager Yes Yes Yes Yes Yes Yes
    HashiCorp Vault Yes Yes Yes

    Shared storage means processes address the same provider resource and have permission to access it; memory is private to one process. Permanent deletion describes removal through the contract, not erasure of provider backups or application copies. Never use deletion as a rotation step.

    Observed mutations are the facade’s payload-free telemetry and optional notifications. Native access and raw driver calls bypass them. All drivers support the facade’s typed JSON definitions and literal namespaces, subject to their own naming and payload rules. A namespace does not create provider resources or permissions.

    Memory

    The bundled memorysecret driver keeps a version history in process memory. Use synthetic values only: it is neither durable storage nor a protected vault.

    Connecting

    Seed a reporting credential for an isolated test:

    import (
    	"arkoren.dev/core/service/secret"
    	"arkoren.dev/core/service/secret/memorysecret"
    )
    
    driver := memorysecret.New(func(fixtures *memorysecret.Builder) {
    	fixtures.Values(map[string][]byte{
    		"reporting-client": []byte(`{"account":"local","token":"fixture"}`),
    	})
    })
    secrets := secret.New(driver)

    Each seed starts at version "1". Values replaces and copies the map and byte slices; nil or empty clears the seeds, and omitting it starts empty. A retained construction draft cannot change the published store. There are no start/stop hooks, native clients, timeout settings, or retry settings.

    Use the typed credential read with this fixture. The returned fields are local and fixture; do not print real credentials in an equivalent production read.

    Storage Limits

    Operations are concurrency-safe and copy payload bytes at their boundaries. Already-canceled contexts change nothing; missing names or versions wrap secret.ErrNotFound. Process exit loses all history. Deletion removes a name’s history immediately, and recreating it starts again at version "1"; an old name/version selector can therefore select different contents.

    See the memorysecret API reference.

    AWS Secrets Manager

    Choose AWS Secrets Manager for workloads with an explicitly configured AWS identity. Runtime reads need secretsmanager:GetSecretValue on the required resources; customer-managed encryption keys can require KMS permissions too. Keep create, write, and delete permissions on provisioning identities.

    Connecting

    From your application module, install the driver:

    go get arkoren.dev/driver/aws

    The AWS SDK discovers credentials and region through its standard sources, including environment, shared configuration, and workload roles. Override the region when the deployment does not supply it:

    import (
    	"arkoren.dev/core/service/secret"
    	"arkoren.dev/driver/aws/awssecret"
    )
    
    driver, err := awssecret.New(ctx, func(provider *awssecret.Builder) {
    	provider.Region("eu-west-1")
    })
    secrets := secret.New(driver)

    Construction loads SDK configuration; it does not test secret access. No driver lifecycle hooks are required. Use the startup read with a provisioned name such as reporting-token.

    Provider Settings

    Credentials supplies a shared credential provider instead of the default chain; Endpoint overrides the service URL. Bound calls with operation contexts. For transport, retries, and timeouts beyond those settings, configure a native SDK client. The deprecated Name builder method is a compatibility no-op; it does not select a default secret or namespace.

    The driver accepts AWS secret names or supported identifiers. Reads accept text or binary values; writes through Arkoren use binary payloads. Explicit Reference.Version is an AWS VersionId, not a movable staging label. Missing and pending-deletion secrets map to secret.ErrNotFound.

    Delete schedules deletion using AWS’s recovery window and does not immediately free the name. A later same-name resource can have a different ARN. Keep the logical secret and requested version retained for pinned reads.

    Native Access

    If your application already owns an aws.Config named config, wrap its client:

    import "github.com/aws/aws-sdk-go-v2/service/secretsmanager"
    
    driver := awssecret.NewFrom(secretsmanager.NewFromConfig(config))
    native := driver.Client()

    native provides SDK operations such as describing secrets. The driver does not copy or close it. Client() returns nil for a nonnative test double. Native operations bypass contract error mapping and facade observations.

    The reporting-only awssecret.Config masks credential-provider contents by type without retrieving credentials or invoking their formatter. Its endpoint and name text, native objects, and returned errors are not sanitized. Keep credentials out of endpoint URLs and ordinary diagnostics.

    Local Checks

    With Docker, run an isolated MiniStack instance bound only to loopback. These credentials are synthetic and must never be used as production credentials:

    docker run --rm --name arkoren-secrets-aws -p 127.0.0.1:4566:4566 -e AWS_ACCESS_KEY_ID=000000000000 -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 ministackorg/ministack:1.5.8

    After readiness, use this alternative construction in your local application:

    import "github.com/aws/aws-sdk-go-v2/credentials"
    
    driver, err := awssecret.New(ctx, func(provider *awssecret.Builder) {
    	provider.Region("us-east-1")
    	provider.Endpoint("http://localhost:4566")
    	provider.Credentials(credentials.NewStaticCredentialsProvider("000000000000", "test", ""))
    })
    secrets := secret.New(driver)

    Run the disposable version check with this facade. It verifies a synthetic write and pinned read, not AWS IAM permissions. Stop the container with docker stop arkoren-secrets-aws; --rm discards its data.

    See the awssecret API reference.

    Azure Key Vault

    Choose Key Vault when you have a vault and an Azure identity with secret access, such as the Key Vault Secrets User role for runtime reads. Grant provisioning permissions separately; default credential discovery is not an access check.

    Connecting

    Install the driver from your application module:

    go get arkoren.dev/driver/azure

    Supply the vault’s absolute HTTPS URL. The driver uses Azure’s default credential chain, including supported developer and managed identities:

    import (
    	"arkoren.dev/core/service/secret"
    	"arkoren.dev/driver/azure/azuresecret"
    )
    
    driver, err := azuresecret.New(ctx, "https://my-vault.vault.azure.net/")
    secrets := secret.New(driver)

    Construction validates the URL and creates SDK objects without contacting the vault. There are no builder settings or driver lifecycle hooks. Use a startup read to verify access to the provisioned name reporting-token.

    Provider Limits

    Names are flat and allow letters, digits, and hyphens, not slash paths. Values must be valid UTF-8; invalid bytes fail locally before a native write. Encode binary material, for example as base64, and decode it after reading. Missing names and versions wrap secret.ErrNotFound.

    Update first reads for existence and then writes, so it requires read and write permission. It has no native version precondition and is not an atomic compare-and-set operation. Already-canceled operations fail before local UTF-8 or SDK parameter validation without a provider request.

    Delete follows the vault’s soft-delete retention. Writing a soft-deleted name wraps azuresecret.ErrSoftDeleted; recovery restores the retained resource. Do not automatically purge it to make provisioning succeed: purge is irreversible and can be blocked by purge protection. Each value write creates a new version; metadata and availability can still change independently.

    Native Access

    For custom credentials, transport, retry, or timeout policy, use an existing azcore.TokenCredential named credential and your HTTPS vaultURL:

    import "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
    
    native, err := azsecrets.NewClient(vaultURL, credential, nil)
    driver := azuresecret.NewFrom(native)

    Replace nil with SDK client options when needed. driver.Client() returns the shared client for recovery and other administration outside the contract; the driver neither copies nor closes it. Keep SDK body logging and native error details out of ordinary diagnostics. No offline emulator setup is provided by this driver; use memory for fixture tests.

    See the azuresecret API reference.

    Google Cloud Secret Manager

    Choose Secret Manager when your project has its API enabled and the runtime identity has Secret Manager Secret Accessor permission on the required secrets. Creating resources, adding versions, and deleting require separate permissions.

    Connecting

    Install the driver from your application module:

    go get arkoren.dev/driver/gcp

    The SDK uses Application Default Credentials. Configure the project for short names such as reporting-token:

    import (
    	"arkoren.dev/core/service/secret"
    	"arkoren.dev/driver/gcp/gcpsecret"
    )
    
    driver, err := gcpsecret.New(ctx, func(provider *gcpsecret.Builder) {
    	provider.Project("my-project")
    })
    secrets := secret.New(driver)

    Register driver.Stop with application cleanup immediately after successful construction. It owns the native gRPC connection; stop consumers before releasing it. The construction context controls SDK construction and credential discovery, not future calls. Verify permissions with the startup read.

    New installs a discard logger for SDK request/response and credential logging, even if GOOGLE_SDK_GO_LOGGING_LEVEL=debug is set. This does not disable the facade’s separate payload-free observations.

    Provider Settings

    Project defaults to empty and is not inferred from credentials. Either set it for unqualified secret IDs or use full names such as projects/my-project/secrets/reporting-token. Fully qualified names keep their explicit project. Slashes belong to that resource path, not inside a secret ID. Use operation contexts to bound calls; native client settings own retry and timeout policy.

    Put creates an absent secret with automatic replication and then adds a version. A failed version write can leave an empty resource. Update only adds a version to an existing resource. Empty version selects latest; concrete versions returned by the driver are strings such as "1", not resource paths. Missing resources or versions wrap secret.ErrNotFound.

    Delete removes the resource and its versions. Recreation can reuse numeric version selectors; a movable alias such as latest never pins contents.

    Native Access

    If you configure SDK endpoint, credentials, retries, or transport yourself, NewFrom preserves that client’s logging policy.

    SDK debug logging can include secret payloads, including when enabled by GOOGLE_SDK_GO_LOGGING_LEVEL. Configure a discard logger before supplying a native client, after other client options so that it takes precedence.

    With existing options of type []option.ClientOption, construct the client:

    import (
    	"log/slog"
    
    	secretmanager "cloud.google.com/go/secretmanager/apiv1"
    	"google.golang.org/api/option"
    )
    
    clientOptions := append(options, option.WithLogger(slog.New(slog.DiscardHandler)))
    native, err := secretmanager.NewClient(ctx, clientOptions...)

    After checking the error, wrap it with the naming policy:

    driver := gcpsecret.NewFrom(native, func(provider *gcpsecret.Builder) {
    	provider.Project("my-project")
    })

    driver.Client() exposes native version administration. Stop closes supplied clients too: keep one cleanup owner and do not share the client with consumers that outlive this driver. Native close has no context parameter, so an expired Stop context still initiates release but cannot bound SDK connection/resolver teardown. Use memory for offline fixtures; this driver supplies no emulator setup.

    See the gcpsecret API reference.

    HashiCorp Vault

    Choose Vault when you operate a KV v2 mount and an authentication policy for your application. Runtime readers need access to its data paths; provisioning tools need appropriate write and metadata permissions too. This driver does not use Vault’s dynamic-secret engines.

    Connecting

    Install the driver from your application module:

    go get arkoren.dev/driver/vault

    For managed login, supply an existing Vault SDK api.AuthMethod named loginMethod. For example, the separate Kubernetes auth SDK requires a Vault role bound to the workload’s service account and namespace. Configure that role and method before constructing the driver:

    import (
    	"arkoren.dev/core/service/secret"
    	"arkoren.dev/driver/vault/vaultsecret"
    )
    
    driver, err := vaultsecret.New(func(provider *vaultsecret.Builder) {
    	provider.Address("https://vault.internal:8200")
    	provider.Auth(loginMethod)
    })
    secrets := secret.New(driver)

    Construction performs SDK validation but no login. Register driver.Stop as cleanup and driver.Start as startup through the application lifecycle. Register credential loading after Start succeeds. With managed authentication, operations before successful Start or after Stop return vaultsecret.ErrNotStarted.

    Authentication Lifetime

    Mount defaults to secret; Address follows SDK defaults including VAULT_ADDR. The SDK can load VAULT_TOKEN; a nonempty Token setting overrides it. Static tokens are never renewed by the driver. With Auth, successful Start replaces the token with the login result.

    Renewable login tokens are renewed in the background. When renewal ends, the driver logs in again, retrying with exponential waits from one second to one minute. A nonrenewable login token is not automatically refreshed. Repeated Start while ready is a no-op; malformed native renewability fails Start. Logger defaults to slog.Default() and reports failed stages and retry delays, not native diagnostics, tokens, or response bodies. Returned errors are not sanitized.

    Start and Stop serialize. Call Stop after Start returns; it cannot cancel a blocked authentication callback. Stop signals managed renewal and waits for its owner and native watcher within the caller’s wait budget. An expired context still signals cleanup, but an in-flight native renewal RPC depends on SDK timeouts and transport cooperation. Ownership remains tracked after an early return; restart is rejected until that worker drains, and a later Stop can wait again. The native HTTP transport is not closed by the driver.

    JSON Credentials

    Vault requires a non-null JSON object. With a provisioned reporting-client object, use the typed read. String would return the entire JSON object as text, not its token field. Names are KV v2 paths relative to the selected mount; do not add the mount’s API envelope yourself.

    Reads re-encode objects, so whitespace and member order can change. Writes reject numbers whose value changes under native float64 JSON serialization, including nested numbers. Store exact identifiers or decimals as JSON strings; the driver does not convert them for you. For example, a provisioning tool can encode this input with encoding/json/v2 before writing it:

    type PartnerCredential struct {
    	AccountID string `json:"account_id"`
    	Token     string `json:"token"`
    }
    
    input := PartnerCredential{AccountID: "9007199254740993", Token: "fixture"}

    Accepted numeric tokens remain numbers and are sent unchanged; equivalent notations such as 1e3 and 1000 are accepted. Existing data is not migrated, and reads cannot recover precision lost by an earlier writer or backend.

    Version Limits

    Explicit selectors must be positive decimal versions. Empty selects current; zero, negative, or unparseable selectors wrap secret.ErrNotFound. Missing, deleted, or destroyed payloads also wrap that sentinel. Already-canceled calls fail before version/JSON validation and provider requests.

    Update reads the current version and uses its number as a native CAS precondition, requiring read and write access. Concurrent changes fail without a driver-level retry; detected absence after CAS rejection wraps secret.ErrNotFound. Metadata deletion and recreation can restart at version 1, so this is not a cross-recreation identity check. Native SDK retry policy remains unchanged. Delete permanently deletes metadata and all history, not just the latest value through Vault’s soft-delete API.

    Native Access

    If you own authentication, logging, transport, and token renewal, wrap your existing *api.Client named native from github.com/hashicorp/vault/api:

    driver := vaultsecret.NewFrom(native.KVv2("application"))
    versions := driver.Client()

    versions is the shared native *api.KVv2, usable for version administration. Client() is nil for nonnative doubles. This path has no managed login or renewal; Start/Stop are no-ops, as they are for static-token drivers. You retain transport cleanup. Native calls bypass payload validation, error mapping, and facade observation. The reporting-only vaultsecret.Config masks its static Token, not Address or a shared Auth formatter.

    Local Checks

    Run an isolated development server with Docker. Its root token and HTTP transport are intentionally insecure: bind loopback and use only synthetic secrets.

    docker run --rm --name arkoren-secrets-vault -p 127.0.0.1:8200:8200 hashicorp/vault server -dev -dev-root-token-id=dev -dev-listen-address=0.0.0.0:8200

    After readiness, the server has a secret KV v2 mount. Use this alternative construction for the disposable check:

    driver, err := vaultsecret.New(func(provider *vaultsecret.Builder) {
    	provider.Address("http://localhost:8200")
    	provider.Token("dev")
    })
    secrets := secret.New(driver)

    Run the JSON version check. A successful pinned read checks this local mount, not production authentication or TLS policy. Stop the server with docker stop arkoren-secrets-vault; --rm discards its data.

    See the vaultsecret API reference.

    For shared reads and writes, see the secret API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive