authotel

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 9 Imported by: 0

README

authotel

authotel is the optional OpenTelemetry adapter for authentication. It turns completed authentication attempts into bounded traces and metrics. It does not authenticate credentials, make authorization decisions, configure an SDK, or own exporters.

Quick start

instrumenter, err := authotel.New(authotel.Config{
	TracerProvider: tracerProvider,
	MeterProvider:  meterProvider,
})
if err != nil {
	return err
}

authenticator, err := authentication.NewInstrumented(
	baseAuthenticator,
	instrumenter,
	clock,
)

Both providers are required and supplied explicitly. authotel never consults OpenTelemetry global providers. Use the OpenTelemetry no-op providers to disable emission while retaining the same wiring.

API and ownership

  • Config carries a caller-owned trace.TracerProvider and metric.MeterProvider.
  • New creates the instruments and maps provider panics or instrument construction failures to fixed invalid-configuration errors without formatting provider error or panic values.
  • Instrumenter.Start implements authentication.Instrumenter and returns the OpenTelemetry span context plus one completion callback.

The caller owns provider configuration, sampling, readers, exporters, queueing, force-flush, and shutdown. Constructing or using this adapter starts no goroutines. The adapter remains usable according to the supplied providers' contract after their shutdown; it performs no independent shutdown work. A provider that returns a nil context is treated as hostile and the caller's original context is preserved.

Telemetry convention

The instrumentation scope is github.com/faustbrian/go-authentication/authotel. The adapter telemetry convention documented below is version 1.0.0. That convention is not a published OpenTelemetry schema and is therefore not written to InstrumentationScope.Version or SchemaURL; those fields are reserved for the instrumentation module version and a resolvable schema URL respectively.

Signal Name Unit Attributes
Span authentication.authenticate n/a credential kind, outcome, failure kind
Counter authentication.attempts {attempt} credential kind, outcome, failure kind
Histogram authentication.duration s credential kind, outcome, failure kind

The duration is recorded once at completion and negative values are clamped to zero. Failed spans use OpenTelemetry error status with the fixed description authentication failed. No application error is recorded.

Closed attribute values
Attribute Values
authentication.credential.kind basic, bearer, api_key, unknown
authentication.outcome authenticated, anonymous, failed, unknown
authentication.failure.kind none, absent, invalid, rejected, unavailable, ambiguous, unknown

none is emitted for authenticated and anonymous outcomes. Missing or unrecognized failure kinds on failed outcomes become unknown. Invalid or future enum values never pass through as attributes, so the maximum attribute combination space is the documented upper bound of 112.

Convention 1.x preserves signal names, units, meanings, and existing attribute values. Additive closed values require a minor convention version and a changelog entry. Renames, removals, unit changes, or meaning changes require a new major convention version and migration guidance. The module remains stable at v1, so consumers should pin an exact module version independently of the telemetry convention version.

Privacy and security

The adapter accepts only authentication.CredentialKind and authentication.Event; it never receives credential payloads, principals, claims, issuers, subjects, endpoints, API keys, tokens, or arbitrary errors. Unknown enum strings are normalized rather than recorded. Panic values from providers or observers are recovered without being formatted into telemetry or returned errors.

Do not add caller-controlled identity or protocol strings as attributes. If a deployment needs such data for debugging, keep it outside this shared adapter and apply its own redaction and cardinality policy.

Completion, cancellation, and failure isolation

The completion callback is exactly-once: its first call records the event and ends the span; duplicate calls emit nothing and return without waiting for the winning completion's telemetry operations. Instrumenter and callbacks are safe for concurrent use. The winning call releases the captured request context and span before recording through stack-local references, so retaining a completed callback does not retain request state. No lock is held across provider or caller code.

A canceled context is preserved and completion is still attempted. This lets a provider record the canceled attempt according to its own SDK and sampling policy. The adapter does not retry, buffer, wait for exporters, or replace the context.

Runtime observer panics are isolated per metric and span operation so one failed observation does not suppress the remaining observations or escape the completion callback. A panic while starting a span returns the original context and a no-op completion callback. The core authentication.Instrumented decorator provides an additional isolation boundary and always returns the wrapped authenticator's original result and error.

OpenTelemetry recording APIs do not return exporter errors, so exporter health, SDK queues, sampling decisions, and shutdown cannot become authentication results through this adapter. Those calls are synchronous: a provider using a synchronous or blocking span processor can delay completion. Applications MUST use no-op, bounded batch, or otherwise non-blocking processors on request paths. The adapter cannot contain an indefinitely blocking provider without starting unbounded goroutines, which this contract forbids.

This is a supported-provider prerequisite: every provider and observer method invoked by the adapter MUST return within the application's request-path bound. An indefinitely blocking implementation is outside the adapter contract. The hostile-provider fuzz matrix covers panics, nil contexts, invalid construction, and observer failures; bounded batch-exporter backpressure is exercised with an actually blocked exporter. Isolating an arbitrary implementation that never returns would require an unbounded abandoned goroutine, contradicting the adapter's no-goroutine and no-leak requirements.

Hardening and allocation inventory

The production dependency surface is the core authentication contract and the OpenTelemetry attribute, metric, and trace APIs. OpenTelemetry SDK packages are used only by tests and benchmarks; applications select and own their SDK configuration. Construction invokes the caller's meter and tracer providers and creates exactly one counter and one histogram. Per attempt, the adapter starts one span and returns one exactly-once callback. Completion performs two span mutations, one counter addition, one histogram recording, and one span end. Every provider or observer panic boundary is isolated independently and all returned adapter errors use fixed text.

The adapter owns no goroutine, exporter, queue, cache, registry, or request collection. Before completion, its callback state is a finite credential-kind attribute, an atomic completion flag, request references, and the two metric instruments needed to record the event. The winning completion clears the request, span, and instrument references before invoking observers through stack-local copies. Metric labels are limited to the documented 112 combinations. Exporter queues and retained exported spans or metrics belong to the supplied SDK.

BenchmarkAuthenticationInstrumentation reports latency and allocations for equivalent direct core instrumentation, OpenTelemetry no-op providers, an SDK with tracing sampled out, and an enabled SDK. The figures are regression evidence for the executing environment, not a cross-platform performance promise. The module benchmark gate runs five 1,000-operation samples per path, compares median latency with the direct-instrumentation sample from the same process, and enforces platform-tolerant maximum ratios of 100x for no-op, 150x for sampled-out, and 200x for enabled telemetry. It also caps those paths at median values of 20, 22, and 24 allocations per operation respectively.

Adoption and tradeoffs

Create one adapter from application-owned, bounded providers and reuse it across instrumented authenticators. Prefer the no-op providers for deployments that disable telemetry; they avoid SDK queues and exporter work while preserving the explicit dependency graph. Prefer a bounded batch processor over a synchronous exporter for request-path tracing.

The adapter deliberately omits credential identities, issuer and subject dimensions, endpoints, error messages, events containing payloads, global provider discovery, authorization decisions, and exporter lifecycle helpers. This limits diagnostic detail in exchange for bounded cardinality and a small secret-safe surface.

Compatibility and migration

The public Go API is tracked by api/baseline.txt. Applications own the providers, so changing exporters or SDK processors does not require an authotel migration. When a telemetry convention major version changes, migration guidance will identify query, dashboard, alert, and collector changes in CHANGELOG.md.

FAQ

Does authotel change authentication outcomes?

No. The authentication decorator preserves the wrapped result and error, and telemetry failures are isolated.

What happens when completion is called twice?

Only the first call emits observations and ends the span.

Who shuts down the providers?

The application that supplied them. authotel owns no provider or exporter.

Can I add a subject, issuer, key ID, route, or error message attribute?

Not through this adapter. Those values violate its privacy or finite-cardinality contract.

How do I disable telemetry?

Supply trace/noop.NewTracerProvider() and metric/noop.NewMeterProvider().

Development

From the repository root, run the affected module contract with:

make check MODULES=authotel

The module requires exact statement coverage and exact viable-mutant kills in addition to formatting, analysis, race, fuzz, security, compatibility, documentation, and benchmark gates.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package authotel adapts authentication instrumentation to bounded, payload-free OpenTelemetry traces and metrics. Providers and their lifecycle remain caller-owned; the package does not use global providers.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	TracerProvider trace.TracerProvider
	MeterProvider  metric.MeterProvider
}

Config supplies caller-owned providers. Both providers are required and their synchronous construction and recording operations must be bounded.

type Instrumenter

type Instrumenter struct {
	// contains filtered or unexported fields
}

Instrumenter emits bounded authentication traces and metrics. It is safe for concurrent use and starts no goroutines.

func New

func New(config Config) (instrumenter *Instrumenter, err error)

New creates OpenTelemetry authentication instrumentation. It creates but does not own, flush, or shut down instruments or providers.

Example
package main

import (
	"context"
	"fmt"

	authentication "github.com/faustbrian/go-authentication"
	"github.com/faustbrian/go-authentication/authotel"

	metricnoop "go.opentelemetry.io/otel/metric/noop"

	tracenoop "go.opentelemetry.io/otel/trace/noop"
)

func main() {
	instrumenter, err := authotel.New(authotel.Config{
		TracerProvider: tracenoop.NewTracerProvider(),
		MeterProvider:  metricnoop.NewMeterProvider(),
	})
	if err != nil {
		fmt.Println("configuration error")
		return
	}
	_, finish := instrumenter.Start(context.Background(), authentication.CredentialBearer)
	finish(authentication.Event{Outcome: authentication.OutcomeAuthenticated})
	fmt.Println("instrumented")
}
Output:
instrumented

func (*Instrumenter) Start

func (i *Instrumenter) Start(
	ctx context.Context,
	kind authentication.CredentialKind,
) (next context.Context, finish func(authentication.Event))

Start implements authentication.Instrumenter. The returned completion callback records only its first invocation and is safe for concurrent use.

Jump to

Keyboard shortcuts

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