Complete reference for the Chaperone Plugin SDK interfaces, types, and
helper methods. The SDK is a separate Go module
(github.com/cloudblue/chaperone/sdk) versioned independently from the
Core (see DESIGN-SPECIFICATION,
ADR-004).
Go standard library types used in signatures link to official
documentation: context.Context, *http.Request,
*http.Response, time.Time, time.Duration,
io.Writer, net.IP.
type Plugin interface {
CredentialProvider
CertificateSigner
ResponseModifier
}A Plugin must implement all three sub-interfaces: CredentialProvider, CertificateSigner, and ResponseModifier. For features you don't need, provide minimal stub implementations (see Plugin Development Guide).
type CredentialProvider interface {
GetCredentials(ctx context.Context, tx TransactionContext, req *http.Request) (*Credential, error)
}Called for each proxied request to inject authentication credentials.
| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Bounded by the Core with a request timeout; cancelled if the upstream client disconnects. Implementations making network calls should respect this context. |
tx |
TransactionContext |
Metadata extracted from inbound request headers (vendor, product, subscription, target URL, etc.). |
req |
*http.Request |
The outgoing HTTP request. Slow Path plugins mutate this directly (e.g., add a body signature header). |
| Return | Type | Description |
|---|---|---|
| credential | *Credential |
Fast Path: headers to inject + cache TTL. Slow Path: return nil (mutate req instead). |
| err | error |
Any error during credential retrieval. |
| Strategy | Return Value | When to Use |
|---|---|---|
| Fast Path | *Credential with headers + TTL |
Static tokens, API keys, Bearer tokens |
| Slow Path | nil, nil (mutate req directly) |
HMAC body signing, request-dependent auth |
Fast Path credentials are cached by the proxy using a hash of the
TransactionContext. Subsequent requests with the
same context are served from cache without calling your plugin again —
until the Credential's ExpiresAt time passes.
Slow Path plugins run on every request. The proxy automatically detects headers added by your plugin (via pre/post snapshot diffing) and ensures they are redacted from logs and stripped from responses.
type CertificateSigner interface {
SignCSR(ctx context.Context, csrPEM []byte) (crtPEM []byte, err error)
}Called when the proxy's TLS certificate approaches expiration and needs rotation. Your implementation should forward the CSR to a Certificate Authority (Connect Portal, HashiCorp Vault, internal PKI).
| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Bounded by the Core with a timeout for the signing operation. |
csrPEM |
[]byte |
PEM-encoded Certificate Signing Request generated by the Core. |
| Return | Type | Description |
|---|---|---|
| crtPEM | []byte |
PEM-encoded signed certificate from the CA. |
| err | error |
Any error during signing. |
type ResponseModifier interface {
ModifyResponse(ctx context.Context, tx TransactionContext, resp *http.Response) (*ResponseAction, error)
}Called after the vendor responds but before the response is returned to the upstream platform. Use cases include stripping PII, normalizing error codes, or passing through ISV validation errors.
| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Request-scoped context with timeout. |
tx |
TransactionContext |
Metadata for this request (same instance passed to GetCredentials). |
resp |
*http.Response |
The vendor's HTTP response. Can be modified in place. Note: reading resp.Body buffers the entire response into memory. |
Warning: Reading
resp.Bodybuffers the entire response into memory. For large vendor responses, consider streaming or limiting the read size.
| Return | Type | Description |
|---|---|---|
| action | *ResponseAction |
Instructions for Core, or nil for default behavior (Core applies error normalization). |
| err | error |
Any error during modification (logged by Core; response is still sent). |
Extracted from inbound request headers and passed to your plugin:
type TransactionContext struct {
Data map[string]any // Additional ISV-specific context (Base64-encoded JSON)
TraceID string // Correlation ID for distributed tracing
EnvironmentID string // Environment identifier (e.g., "production", "test")
MarketplaceID string // Marketplace identifier (e.g., "US", "EU")
VendorID string // Vendor account ID
ProductID string // Product SKU
SubscriptionID string // Subscription identifier
TargetURL string // Destination URL (validated against allow-list)
}| Field | Type | Description |
|---|---|---|
Data |
map[string]any |
Additional ISV-specific context. Deserialized from a Base64-encoded JSON header. |
TraceID |
string |
Correlation ID for distributed tracing. Auto-generated UUID if not present in the request. |
EnvironmentID |
string |
Environment identifier (e.g., "production", "test"). |
MarketplaceID |
string |
Marketplace identifier (e.g., "US", "EU"). |
VendorID |
string |
Vendor account ID. |
ProductID |
string |
Product SKU. |
SubscriptionID |
string |
Subscription identifier. |
TargetURL |
string |
Destination URL for this request. Already validated against the allow-list by the Core. |
func (tx TransactionContext) DataString(field string) (value string, ok bool, err error)Returns the tx.Data[field] string value when present and valid.
Return values:
value: the string when present and validok:truewhen the field is present,falsewhen absenterr:ErrInvalidContextDatawhen present but wrong type or empty
Behavior:
- If
tx.Data[field]is present and is a valid non-empty string, returns(value, true, nil). - If present but has the wrong type or is an empty string, returns
("", true, ErrInvalidContextData). - If absent, returns
("", false, nil).
Use this helper to validate optional/required fields while keeping "missing field" policy at the call site (see DataString usage example).
These fields are extracted from headers using the configured prefix
(default X-Connect-):
| Header | Field |
|---|---|
X-Connect-Vendor-ID |
VendorID |
X-Connect-Environment-ID |
EnvironmentID |
X-Connect-Product-ID |
ProductID |
X-Connect-Marketplace-ID |
MarketplaceID |
X-Connect-Subscription-ID |
SubscriptionID |
X-Connect-Target-URL |
TargetURL |
X-Connect-Context-Data |
Data (Base64-encoded JSON) |
Connect-Request-ID |
TraceID |
type Credential struct {
Headers map[string]string // Authentication headers to inject
ExpiresAt time.Time // Cache expiry time
}| Field | Type | Description |
|---|---|---|
Headers |
map[string]string |
Key-value pairs of headers to inject into the outgoing request (e.g., "Authorization": "Bearer <token>"). |
ExpiresAt |
time.Time |
When this credential should be evicted from cache. |
- Set slightly before actual token expiry (e.g., token expires in 1h → set 55m)
- For non-expiring API keys, use a reasonable refresh interval (e.g., 24h)
- Never set to zero — use a minimum of 1 minute
func (c *Credential) IsExpired() boolReturns true if the credential has passed its expiration time.
Returns true if c is nil.
func (c *Credential) TTL() time.DurationReturns the remaining time.Duration until the credential expires.
Returns 0 if already expired or if c is nil.
type ResponseAction struct {
SkipErrorNormalization bool
}| Field | Type | Default | Description |
|---|---|---|---|
SkipErrorNormalization |
bool |
false |
When true, Core passes the vendor's response body as-is for error responses (4xx/5xx). Core still strips sensitive headers. |
Use SkipErrorNormalization: true when:
- The ISV returns structured validation errors that the upstream platform needs
- Your plugin has already sanitized the error response
var ErrInvalidContextData = errors.New("invalid context data type")Indicates a transaction context field is present but fails validation (wrong type or empty string).
Used by DataString and other context validation functions.
Check with errors.Is:
value, ok, err := tx.DataString("TenantID")
if errors.Is(err, sdk.ErrInvalidContextData) {
// Field present but invalid (wrong type or empty)
}The Core module (github.com/cloudblue/chaperone) provides the entry
points for running the proxy.
func Run(ctx context.Context, plugin sdk.Plugin, opts ...Option) errorStarts the Chaperone proxy and blocks until the context is cancelled or a fatal error occurs. This is the primary entry point for Distributors building custom binaries.
| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Controls the proxy's lifecycle. Cancel to trigger graceful shutdown. |
plugin |
Plugin |
Your credential injection implementation. Pass nil to run without credential injection. |
opts |
...Option |
Zero or more option functions to configure behavior. |
| Return | Type | Description |
|---|---|---|
| err | error |
nil on clean shutdown (context cancelled). Non-nil for configuration or startup failures. |
func Enroll(ctx context.Context, cfg EnrollConfig) (*EnrollResult, error)Generates an ECDSA P-256 key pair and Certificate Signing Request for production CA enrollment. The CSR can be submitted to a CA (Connect Portal, HashiCorp Vault, internal PKI) to obtain a signed server certificate for mTLS.
| Parameter | Type | Description |
|---|---|---|
ctx |
context.Context |
Reserved for future use (e.g., remote CA interaction). |
cfg |
EnrollConfig |
Configuration for CSR generation. |
| Return | Type | Description |
|---|---|---|
| result | *EnrollResult |
Paths to generated files and SAN details. |
| err | error |
Any error during key generation, CSR creation, or file writing. |
Options are functions passed to Run to configure optional
behavior. Each returns an Option value
(functional options pattern).
func WithConfigPath(path string) OptionSets the path to the YAML configuration file.
| Parameter | Type | Description |
|---|---|---|
path |
string |
Absolute or relative path to the config file. |
If not set, Chaperone resolves the config path in this order:
CHAPERONE_CONFIGenvironment variable./config.yamlin the current directory
func WithVersion(version string) OptionSets the version string reported by the /_ops/version endpoint and
included in startup logs.
| Parameter | Type | Description |
|---|---|---|
version |
string |
Version string (e.g., "1.5.0"). Defaults to "dev". |
func WithBuildInfo(commit, buildDate string) OptionSets git commit and build date metadata included in startup logs.
| Parameter | Type | Description |
|---|---|---|
commit |
string |
Git commit hash (e.g., "abc1234"). |
buildDate |
string |
Build timestamp (e.g., "2026-01-15T10:30:00Z"). |
func WithLogOutput(w io.Writer) OptionSets the output destination for structured logs.
| Parameter | Type | Description |
|---|---|---|
w |
io.Writer |
Log output writer. Defaults to os.Stdout. Useful for testing or custom log routing. |
type EnrollConfig struct {
Domains string // Comma-separated DNS names and IPs for SANs
CommonName string // Certificate Common Name (default: "chaperone")
OutputDir string // Output directory (default: "certs")
Force bool // Overwrite existing files
}| Field | Type | Default | Description |
|---|---|---|---|
Domains |
string |
— (required) | Comma-separated DNS names and IP addresses for SANs. Example: "proxy.example.com,10.0.0.1" |
CommonName |
string |
"chaperone" |
Certificate Common Name. |
OutputDir |
string |
"certs" |
Directory for server.key and server.csr. Created if absent. |
Force |
bool |
false |
When true, overwrites existing key and CSR files. When false, returns ErrFileExists if files already exist. |
type EnrollResult struct {
KeyFile string // Path to the generated private key
CSRFile string // Path to the generated CSR
DNSNames []string // DNS SANs included in the CSR
IPs []net.IP // IP SANs included in the CSR
}| Field | Type | Description |
|---|---|---|
KeyFile |
string |
Path to the generated ECDSA P-256 private key. |
CSRFile |
string |
Path to the generated Certificate Signing Request. |
DNSNames |
[]string |
DNS SANs included in the CSR. |
IPs |
[]net.IP |
IP SANs included in the CSR. |
The sdk/compliance package provides a contract test suite for plugin
implementations.
import "github.com/cloudblue/chaperone/sdk/compliance"func VerifyContract(t *testing.T, p sdk.Plugin)Runs contract tests against a plugin to verify it handles edge cases
(empty context, cancelled context, nil CSR, nil response) without
panicking, and that returned credentials have a valid ExpiresAt.
See the Plugin Development Guide for usage in your test suite.
| Module | Import Path | Tag Format | Purpose |
|---|---|---|---|
| SDK | github.com/cloudblue/chaperone/sdk |
sdk/v1.x.x |
Plugin interfaces (stable API) |
| Core | github.com/cloudblue/chaperone |
v1.x.x |
Proxy engine, internal logic |
Distributors can upgrade Core without touching their plugin code, as long as the SDK major version remains the same:
require (
github.com/cloudblue/chaperone/sdk v1.0.0 // Stable interface
github.com/cloudblue/chaperone v1.5.0 // Can upgrade freely
)When the SDK moves to a new major version (e.g., v1 → v2), the Go import path changes per Go module conventions:
| Version | Import Path |
|---|---|
| v0, v1 | github.com/cloudblue/chaperone/sdk |
| v2+ | github.com/cloudblue/chaperone/sdk/v2 |
A major SDK version bump requires coordinated changes:
- SDK module: Update
modulepath insdk/go.modto include/v2 - Core module: Update the
requiredirective and allimportstatements ingo.modand Go source files - Tags: Create new tags (
sdk/v2.0.0, then a Core release) - Distributors: Must update their import paths and
requiredirectives — this is a breaking change
Major version bumps should be rare and coordinated with a Core release. See ADR-004 for the rationale behind independent module versioning.