Skip to content

Repository files navigation

Locker Go SDK

The Locker Go SDK reads and manages Locker Passwords & Secrets through the stable locker.sdk protocol exposed by the Locker CLI.

The SDK intentionally does not duplicate Locker's HTTP client, cryptography, or local database. It starts locker sdk, sends one JSON-RPC 2.0 request over stdin, reads one response from stdout, and terminates the process.

Requirements

  • Go 1.25.12, Go 1.26.5, or a later fully patched supported Go release
  • A Locker CLI release that supports SDK protocol v1
  • LOCKER_ACCESS_KEY_ID
  • LOCKER_SECRET_ACCESS_KEY

Install the library:

go get github.com/lockerpm/secrets-sdk-go/v2/locker

By default the SDK installs and verifies the current Locker CLI from Locker's signed release channel. Set Config.CLIPath or LOCKER_CLI_PATH only when you intentionally supply a caller-owned binary. Package import never performs I/O; managed resolution begins when a client is constructed. Use locker.NewWithContext when startup must be cooperatively cancelled while waiting for signed resolution, network I/O, or the update lock.

Quick start

package main

import (
	"context"
	"log"

	locker "github.com/lockerpm/secrets-sdk-go/v2/locker"
)

func main() {
	client, err := locker.FromEnv()
	if err != nil {
		log.Fatal(err)
	}

	databasePassword, err := client.GetRequired(
		context.Background(),
		"DATABASE_PASSWORD",
		locker.InEnvironment("production"),
	)
	if err != nil {
		log.Fatal(err)
	}

	// Pass databasePassword directly to the database driver. Never log it.
	_ = databasePassword
}

ACCESS_KEY_ID, SECRET_ACCESS_KEY, LOCKER_ACCESS_KEY_SECRET, and ACCESS_KEY_SECRET remain temporary migration aliases. Canonical variables always win.

FromEnv also reads optional LOCKER_API_BASE; LOCKER_CLI_PATH participates in binary resolution. Explicit options take precedence.

Environment variable Purpose
LOCKER_ACCESS_KEY_ID Project access key ID
LOCKER_SECRET_ACCESS_KEY Project secret access key
LOCKER_API_BASE Cloud or self-hosted API base URL
LOCKER_CLI_PATH Absolute caller-owned CLI path

For explicit configuration:

client, err := locker.New(locker.Config{
	Credentials: locker.Credentials{
		AccessKeyID:     accessKeyID,
		SecretAccessKey: secretAccessKey,
	},
	CLIPath: "/opt/locker/bin/locker",
	Timeout: 20 * time.Second,
	Transport: locker.TransportConfig{
		APIBase: "https://api.locker.io/locker_secrets",
		Headers: map[string]string{
			"CF-Access-Client-Id": cfAccessClientID,
		},
	},
})

Configuration maps are copied by New, and a Client is safe for concurrent use.

Timeout, cancellation, retry, and vault cache

Every method accepts a context. The earlier of its deadline/cancellation or Config.Timeout stops CLI resolution, capability negotiation, and the operation, then terminates the CLI process tree.

The SDK never automatically retries a vault RPC or API failure. In particular, create and update are issued once because a lost response can leave the remote commit outcome unknown. Applications may use RPCError.Retryable for a bounded retry policy on read-only operations only.

The SDK does not cache plaintext secrets. CacheConfig delegates encrypted, revision-aware caching to the CLI:

maxAge := 30
client, err := locker.FromEnv(
	locker.WithCache(locker.CacheConfig{MaxAgeSeconds: &maxAge}),
)

MaxAgeSeconds: &0 disables offline reuse. ForceRefresh: true requires a successful server refresh and never falls back. A transient outage may use only a still-fresh cache last validated successfully by the server; authentication, authorization, TLS, integrity, malformed-response, and local storage failures fail closed.

API

All operations accept context.Context, negotiate protocol v1 before the first vault operation, and return typed stable models.

secret, err := client.GetSecret(ctx, "DATABASE_PASSWORD")
secrets, err := client.ListSecrets(ctx, locker.InEnvironment("production"))
secretPage, err := client.ListSecretsPage(
	ctx,
	locker.PageRequest{PageSize: 100},
	locker.InEnvironment("production"),
)

createdSecret, err := client.CreateSecret(ctx, locker.SecretCreateInput{
	Key:         "DATABASE_PASSWORD",
	Value:       value, // Empty values are valid.
	Environment: locker.String("production"),
	Description: locker.String("Primary database password"),
})

updatedSecret, err := client.UpdateSecret(ctx, locker.SecretUpdateInput{
	Key:         "DATABASE_PASSWORD",
	Environment: locker.String("production"),
	Changes: locker.SecretChanges{
		Key:         locker.String("PRIMARY_DATABASE_PASSWORD"),
		Value:       locker.String(newValue),
		Environment: locker.NullString(), // Clear the environment association.
		Description: locker.String(""),
	},
})

environment, err := client.GetEnvironment(ctx, "production")
environments, err := client.ListEnvironments(ctx)
environmentPage, err := client.ListEnvironmentsPage(
	ctx,
	locker.PageRequest{PageSize: 100},
)

createdEnvironment, err := client.CreateEnvironment(
	ctx,
	locker.EnvironmentCreateInput{
		Name:        "production",
		ExternalURL: locker.String("https://example.com"),
		Description: locker.String(""),
	},
)

updatedEnvironment, err := client.UpdateEnvironment(
	ctx,
	locker.EnvironmentUpdateInput{
		Name: "production",
		Changes: locker.EnvironmentChanges{
			Name:        locker.String("production-eu"),
			ExternalURL: locker.String("https://eu.example.com"),
		},
	},
)

GetOrDefault returns its default only when the CLI returns numeric error -32004:

value, err := client.GetOrDefault(ctx, "OPTIONAL_KEY", "fallback")

Authentication, permission, protocol, network, server, rate-limit, and storage failures always fail closed.

Errors

Locker application errors are *locker.RPCError and map by numeric JSON-RPC code:

_, err := client.CreateSecret(ctx, locker.SecretCreateInput{
	Key:   "PAYMENT_API_KEY",
	Value: paymentAPIKey,
})
if errors.Is(err, locker.ErrAlreadyExists) {
	// PAYMENT_API_KEY already exists.
	// ErrAlreadyExists is also an ErrConflict.
}

var rpcError *locker.RPCError
if errors.As(err, &rpcError) && rpcError.Retryable {
	// Apply the application's bounded retry policy.
	// For rate_limited, RetryAfterSeconds may provide a 0..86400 hint.
}

The stable protocol taxonomy is:

Code Go classification Canonical kind
-32700 ErrProtocol parse_error
-32600 ErrProtocol invalid_request
-32601 ErrProtocol method_not_found
-32602 ErrProtocol invalid_params
-32603 ErrProtocol internal_protocol_error
-32000 ErrOperation and legacy subtypes operation_error, request_rejected, response_too_large, cancelled
-32001 ErrAuthentication missing_credentials, invalid_access_key_id, malformed_secret_access_key, invalid_secret_access_key, unauthorized
-32003 ErrPermission forbidden; legacy permission_denied
-32004 ErrNotFound secret_not_found, environment_not_found; legacy not_found_error
-32009 ErrConflict / ErrAlreadyExists conflict, secret_already_exists, environment_already_exists
-32022 ErrValidation validation_error
-32029 ErrRateLimited rate_limited
-32050 ErrNetwork network_error, network_timeout; legacy http_error
-32051 ErrServer service_unavailable, internal_error; legacy server_error
-32060 ErrStorage database_error, file_error, path_error
-32070 ErrIntegrity integrity_error, transport_integrity_error, data_integrity_error; legacy data_error

Classification is numeric-first. For compatibility with older CLI releases, the distinctive legacy -32000 kinds duplicate_hash, *_already_exists, conflict, validation_error, and the integrity aliases retain their typed classification. request_rejected, response_too_large, and cancelled remain explicit ErrOperation subtypes and are never guessed to be conflicts. Known authentication, permission, not-found, conflict, validation, storage, integrity, protocol, cancellation, and internal-server errors are always non-retryable. Only rate-limit, network, service-unavailable, or an unknown server-range code can preserve a true retry hint. The SDK never retries a vault mutation automatically.

Credential syntax is validated before the CLI is resolved, downloaded, or launched. Outer whitespace is removed from both values. The access key ID must be a UUIDv4 and the secret access key must be non-empty canonical standard Base64. Missing or malformed values classify through ErrAuthentication and the more specific ErrMissingAccessKeyID, ErrMissingSecretKey, ErrInvalidAccessKeyID, or ErrMalformedSecretAccessKey sentinel. A well-formed pair rejected by Locker uses invalid_secret_access_key and the safe message the secret access key does not match the access key ID; a backend unauthorized response retains the generic authentication failed message. All five authentication kinds are non-retryable.

Typed errors use additive capability negotiation. The SDK sends context.error_contract = "typed-v1" only when system.capabilities advertises that exact value in error_contracts. An absent list or an unknown valid contract remains compatible and does not opt in. When present, a strictly validated ServerRequestID is the upstream service correlation ID; it is separate from the local JSON-RPC RequestID and is never included in the default error string.

Transport failures are *locker.TransportError; malformed or incompatible CLI responses are *locker.ProtocolViolationError. Error strings never include the request, response, child stderr, credentials, headers, or secret values. Secret.String and Secret.GoString always redact Value.

CLI resolution and managed installation

Resolution order is:

  1. absolute Config.CLIPath
  2. absolute LOCKER_CLI_PATH
  3. the verified generation selected by ~/.locker/sdk-cli/go/current.json

Legacy names such as locker_secret are never resolved automatically. A caller may provide its absolute regular non-symlink path explicitly, but protocol capability negotiation is still mandatory. Bare and relative values are rejected instead of being searched through ambient PATH.

InstallCLI(ctx) installs from the signed release channel. Managed resolution checks immediately on first use and periodically afterward; an explicit InstallCLI call forces a check. The SDK verifies signed release metadata, the artifact's SHA-256 hash and Ed25519 signature, and the executable's declared OS and architecture. Rollback, same-version replacement, signature, provenance, and integrity failures are rejected.

Managed binaries are reverified before execution, stored in a private per-user cache, and activated atomically only after verification succeeds. A transient network failure may use the last fully verified cached release; verification failures always fail closed. Explicit caller-owned CLI paths remain the caller's trust responsibility and do not use managed-channel verification.

Process security

  • The CLI receives exactly one non-sensitive argv value: sdk.
  • Credentials, custom headers, and mutation values exist only in the JSON request on stdin.
  • The child gets a strict allowlist of OS, home, locale, proxy, and certificate environment variables. Locker credentials and arbitrary application variables are stripped.
  • stdout and stderr are separately bounded; stderr is never included in SDK errors.
  • timeouts and cancellations terminate the CLI process tree.
  • Config.Timeout is one end-to-end budget for CLI resolution, capability negotiation, and the vault operation.
  • malformed, oversized, or incorrectly correlated protocol responses are rejected.

Migrating from v1

This refactor is a breaking v2 boundary. See MIGRATION.md.

Versioning and releases

The SDK follows Semantic Versioning. The module major version is part of the import path, and public releases use vMAJOR.MINOR.PATCH tags. Breaking API changes require a new major module path.

Troubleshooting and support

  • ErrAuthentication: verify that the access key ID is UUIDv4, the secret access key is canonical standard Base64, and both values belong to the same active Locker project. ErrMissingAccessKeyID, ErrMissingSecretKey, ErrInvalidAccessKeyID, and ErrMalformedSecretAccessKey identify local syntax failures.
  • *TransportError: inspect its safe kind, the API base, CA/proxy, and CLI path. Error text intentionally excludes CLI stderr and protocol bodies.
  • Managed install failure: check system time, access to the Locker Secrets download service, and private ownership below ~/.locker/sdk-cli/go.
  • *ProtocolViolationError: upgrade the SDK and CLI together or remove an incompatible explicit LOCKER_CLI_PATH.
  • Unexpected stale reads: use ForceRefresh; do not loosen cache permissions.

Product help is available at support.locker.io. Report vulnerabilities through the Locker Bug Bounty program; never attach access keys or plaintext secret values to an issue.

License

The Locker Go SDK is licensed under the Apache License 2.0.

About

No description, website, or topics provided.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages