Skip to content

Latest commit

 

History

History
529 lines (381 loc) · 17.2 KB

File metadata and controls

529 lines (381 loc) · 17.2 KB

SDK Reference

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.


Interfaces

Plugin (Composite Interface)

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).


CredentialProvider

type CredentialProvider interface {
    GetCredentials(ctx context.Context, tx TransactionContext, req *http.Request) (*Credential, error)
}

Called for each proxied request to inject authentication credentials.

GetCredentials Parameters

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).

GetCredentials Return Values

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.

Fast Path vs Slow Path

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.


CertificateSigner

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).

SignCSR Parameters

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.

SignCSR Return Values

Return Type Description
crtPEM []byte PEM-encoded signed certificate from the CA.
err error Any error during signing.

ResponseModifier

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.

ModifyResponse Parameters

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.Body buffers the entire response into memory. For large vendor responses, consider streaming or limiting the read size.

ModifyResponse Return Values

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).

Types

TransactionContext

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)
}

Fields

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.

Helper Methods

DataString
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 valid
  • ok: true when the field is present, false when absent
  • err: ErrInvalidContextData when present but wrong type or empty

Behavior:

  1. If tx.Data[field] is present and is a valid non-empty string, returns (value, true, nil).
  2. If present but has the wrong type or is an empty string, returns ("", true, ErrInvalidContextData).
  3. 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).

Header Mapping

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

Credential

type Credential struct {
    Headers   map[string]string  // Authentication headers to inject
    ExpiresAt time.Time          // Cache expiry time
}

Fields

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.

Best Practices for ExpiresAt

  • 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

Helper Methods

IsExpired
func (c *Credential) IsExpired() bool

Returns true if the credential has passed its expiration time. Returns true if c is nil.

TTL
func (c *Credential) TTL() time.Duration

Returns the remaining time.Duration until the credential expires. Returns 0 if already expired or if c is nil.


ResponseAction

type ResponseAction struct {
    SkipErrorNormalization bool
}

Fields

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

Errors

ErrInvalidContextData

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.

Run

func Run(ctx context.Context, plugin sdk.Plugin, opts ...Option) error

Starts 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.

Run Parameters

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.

Run Return Values

Return Type Description
err error nil on clean shutdown (context cancelled). Non-nil for configuration or startup failures.

Enroll

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.

Enroll Parameters

Parameter Type Description
ctx context.Context Reserved for future use (e.g., remote CA interaction).
cfg EnrollConfig Configuration for CSR generation.

Enroll Return Values

Return Type Description
result *EnrollResult Paths to generated files and SAN details.
err error Any error during key generation, CSR creation, or file writing.

Option Functions

Options are functions passed to Run to configure optional behavior. Each returns an Option value (functional options pattern).

WithConfigPath

func WithConfigPath(path string) Option

Sets 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:

  1. CHAPERONE_CONFIG environment variable
  2. ./config.yaml in the current directory

WithVersion

func WithVersion(version string) Option

Sets 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".

WithBuildInfo

func WithBuildInfo(commit, buildDate string) Option

Sets 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").

WithLogOutput

func WithLogOutput(w io.Writer) Option

Sets 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.

EnrollConfig

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
}

Fields

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.

EnrollResult

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
}

Fields

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.

Compliance Test Kit

The sdk/compliance package provides a contract test suite for plugin implementations.

import "github.com/cloudblue/chaperone/sdk/compliance"

VerifyContract

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 Versioning

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
)

Major Version Upgrades

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:

  1. SDK module: Update module path in sdk/go.mod to include /v2
  2. Core module: Update the require directive and all import statements in go.mod and Go source files
  3. Tags: Create new tags (sdk/v2.0.0, then a Core release)
  4. Distributors: Must update their import paths and require directives — 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.