inject

package
v0.0.0-...-f3da6ff Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package inject binds a host-resolved credential onto an outbound HTTP request according to a closed set of injection [Scheme]s.

It is a sub-package of credential so it can reference the resolved secret bytes without creating an import cycle (credential does not import inject). The injector is a pure function over (scheme, request, secret, params) with zero knowledge of connectors, CLIs, GitHub, AWS, or any specific service. The scheme-keyed dispatch in Inject replaces the per-service/per-kind branching that previously lived at the proxy egress point.

Secret handling

The secret bytes are passed to Inject separately from Params so the param struct can be logged or echoed safely while the secret never can. This package contains no logging calls, and no code path writes the secret into an error message, a return value, or any other observable surface other than the request header/query value it is being injected into. The secret is host-side only and must never reach an audit record or the sandbox guest (see ADR-0005 and ADR-0011).

Scheme set

The five schemes (SchemeBearer, SchemeBasic, SchemeHeaderTemplate, SchemeQueryParam, SchemeSigV4Resign) are the closed set ratified by ADR-0019. SchemeSigV4Resign re-signs the request with an AWS Signature Version 4 signature derived from the secret access key; the signing core is std-library-only (no AWS SDK). ErrSchemeNotImplemented is retained as a sentinel for the closed-set contract but is no longer returned by Inject for any current scheme.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnknownScheme means a string did not name one of the closed-set
	// schemes. Returned by [ParseScheme].
	ErrUnknownScheme = errors.New("inject: unknown injection scheme")

	// ErrSchemeNotImplemented means the scheme is a recognized member of
	// the closed set but its injection logic is deferred. It is retained
	// as a sentinel for the closed-set contract; no current scheme is
	// deferred, so [Inject] does not return it today.
	ErrSchemeNotImplemented = errors.New("inject: injection scheme not implemented")

	// ErrMissingParam means a scheme requires a [Params] field that was
	// not supplied (e.g. basic auth without a username). Returned by
	// [Inject].
	ErrMissingParam = errors.New("inject: required parameter missing")

	// ErrUnparseableAWSHost means an outbound host could not be parsed into
	// an AWS SigV4 (service, region) credential scope: it carries no
	// region-shaped label, or the region-shaped label has no preceding
	// service label. Returned by [ParseAWSEndpointHost]. The host string it
	// names is non-secret (a hostname, never credential bytes).
	ErrUnparseableAWSHost = errors.New("inject: host is not a parseable AWS endpoint")
)

Sentinel errors. Callers pattern-match with errors.Is.

Functions

func Inject

func Inject(req *http.Request, scheme Scheme, secret []byte, params Params) error

Inject binds secret onto req according to scheme. The secret bytes are written only into the request surface the scheme defines (a header value or a query parameter); they never appear in the returned error.

For SchemeSigV4Resign the only required Params field is AccessKeyID (it is non-derivable and appears verbatim in the Credential= scope); the secret argument carries the AWS secret access key, used only as HMAC key material to derive the signing key and never appearing in the resulting Authorization header. The (service, region) credential scope is host-authoritative: it is derived from the request host via ParseAWSEndpointHost when that host parses as an AWS endpoint, so a stored Params.Region/Params.Service that disagrees with the host the proxy actually dials can no longer sign the wrong scope (the UnrecognizedClientException class of bug). When the host does not parse, Params.Region and Params.Service are used as a fallback but only when both are present; if neither the host nor the params yield a full scope the request is left unmutated and ErrMissingParam is returned (fail closed, never a silent default).

Returns ErrMissingParam if a scheme's required Params field is absent and ErrUnknownScheme for any value outside the closed set. On any error the request is left unmutated.

func ParseAWSEndpointHost

func ParseAWSEndpointHost(host string) (service, region string, err error)

ParseAWSEndpointHost derives the AWS SigV4 credential scope (service, region) from an outbound endpoint host such as "athena.us-east-1.amazonaws.com" -> ("athena", "us-east-1"). Any port is stripped and the host is lowercased before parsing.

The algorithm scans the dot-separated labels for the first region-shaped label ([awsRegionPattern]); the region is that label and the service is the nearest preceding label that is not an endpoint modifier. Modifier labels ([endpointInfixModifiers], e.g. "dualstack") are skipped, and a trailing "-fips" is stripped from the service label, so "s3.dualstack.us-west-2.amazonaws.com" and "s3-fips.us-east-1.amazonaws.com" both resolve service "s3" rather than signing against a modifier-corrupted scope. It does not require a ".amazonaws.com" suffix, so test endpoints like "athena.us-east-1.amazonaws.test" parse the same way as production hosts.

It fails with ErrUnparseableAWSHost when the host is empty, carries no region-shaped label (e.g. the legacy global "s3.amazonaws.com" or a non-AWS host), or has a region-shaped leading label with no service label before it. The host string is echoed into the error to name the cause; it is a hostname and never carries credential bytes.

This is the single host->scope parser shared by the SigV4 injector (host-authoritative signing scope) and the WASM host's region-scoped binding selection, so the two cannot drift.

Assumption (host-label == signing service): the derived service is the endpoint host label itself. This holds for every service Aileron signs today (Athena: "athena.<region>.amazonaws.com" -> service "athena"), so the parser returns the host label verbatim as the SigV4 signing service. A handful of AWS services break this assumption because their endpoint host label differs from the SigV4 service name they must be signed as:

  • Amazon SES: "email.<region>.amazonaws.com" is signed as service "ses" (the host label is "email", not "ses").
  • Amazon SimpleDB: "sdb.<region>.amazonaws.com" is signed as service "sdb", and other historical endpoints carry similar host/service skew.

For those divergent-service endpoints the host-authoritative service this function returns (the raw host label, e.g. "email") is not the correct SigV4 signing service (e.g. "ses"). Divergent-service handling is deliberately deferred: none of these services are on Aileron's signing path yet, and the operator decision (consistent with the no-back-compat stance) is to keep the signing service host-authoritative rather than carry a host-label->service lookup table before a concrete need. Adding a divergent-service mapping here is the named follow-up if such a service is ever put on the SigV4 path. Do not change signing behavior for these hosts without that mapping; today they simply parse to their host label.

Types

type Params

type Params struct {
	// Username is the user portion of HTTP basic auth, used only by
	// [SchemeBasic] (e.g. "x-access-token" for git-over-HTTPS). Required
	// for that scheme.
	Username string

	// HeaderName is the header to set, used only by
	// [SchemeHeaderTemplate] (e.g. "Authorization" or a vendor header).
	// Required for that scheme.
	HeaderName string

	// Template is the verbatim header value for [SchemeHeaderTemplate],
	// with the "{token}" placeholder substituted with the secret at
	// inject time. If empty, the header is set to the raw token.
	Template string

	// ParamName is the query-parameter name to set, used only by
	// [SchemeQueryParam]. Required for that scheme.
	ParamName string

	// AccessKeyID is the AWS access key ID, used only by
	// [SchemeSigV4Resign]. It is non-secret (it appears verbatim in the
	// Credential= field of the Authorization header) and is required for
	// that scheme. The secret access key arrives via the secret argument
	// to [Inject], never through this struct.
	AccessKeyID string

	// Region is the AWS region (e.g. "us-east-1"), used only by
	// [SchemeSigV4Resign] for the credential scope. It is a transitional
	// fallback: when the request host parses as an AWS endpoint the region
	// is derived from that host and this field is ignored; it is consulted
	// only when the host is unparseable, and then only together with a
	// non-empty Service. See [Inject].
	Region string

	// Service is the AWS service name (e.g. "s3"), used only by
	// [SchemeSigV4Resign] for the credential scope. Like Region it is a
	// transitional fallback: the host-derived service wins when the request
	// host parses, and this field is consulted only for an unparseable host
	// together with a non-empty Region. See [Inject].
	Service string
}

Params carries the scheme-specific, non-secret inputs to Inject. Every field is safe to log or echo; the secret bytes are never stored here. Each scheme reads only the fields it needs and validates their presence before touching the secret.

type Scheme

type Scheme string

Scheme is the closed set of credential-injection schemes ratified by ADR-0019. A Scheme names *how* a resolved secret is bound onto an outbound HTTP request (e.g. as a bearer header, HTTP basic auth, or a query parameter). The set is intentionally closed: anything outside the enumerated members is rejected by ParseScheme so a typo or an unsupported vendor convention fails fast rather than silently leaking a credential into the wrong place.

const (
	// SchemeBearer sets "Authorization: Bearer <token>".
	SchemeBearer Scheme = "bearer"

	// SchemeBasic sets "Authorization: Basic base64(<username>:<token>)".
	SchemeBasic Scheme = "basic"

	// SchemeHeaderTemplate sets an arbitrary header to a verbatim
	// template value with the "{token}" placeholder substituted.
	SchemeHeaderTemplate Scheme = "header-template"

	// SchemeQueryParam sets a query parameter on the request URL to the
	// token value.
	SchemeQueryParam Scheme = "query-param"

	// SchemeSigV4Resign re-signs the request with an AWS Signature
	// Version 4 signature derived from the secret access key. The signing
	// core is std-library-only (crypto/hmac + crypto/sha256); no AWS SDK
	// dependency is pulled in.
	SchemeSigV4Resign Scheme = "sigv4-resign"
)

func AllSchemes

func AllSchemes() []Scheme

AllSchemes returns a copy of the closed set of schemes, in ADR order.

func ParseScheme

func ParseScheme(s string) (Scheme, error)

ParseScheme maps a string to its Scheme constant. Any value outside the closed set returns a wrapped ErrUnknownScheme.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL