Add auth, security headers & a gateway¶
The transport servers are composed from small building blocks: authentication and
security-header middleware from transport/http (and AuthInterceptor from
transport/grpc), stitched together with a go/transit
Chain, plus an optional grpc-gateway.
Authenticate HTTP requests¶
AuthMiddleware verifies each request against a go/authn
verifier and stores the identity in the request context. It is an ordinary transit
Middleware, so it composes into a Chain:
import (
"gitlab.com/phpboyscout/go/authn"
transithttp "gitlab.com/phpboyscout/go/transit/http"
transporthttp "gitlab.com/phpboyscout/go/transport/http"
)
auth, err := transporthttp.AuthMiddleware(
transporthttp.WithBearerVerifier(jwtVerifier), // authn.Verifier
transporthttp.WithAuthLogger(log),
)
if err != nil {
return err
}
chain := transithttp.NewChain(auth, transporthttp.SecurityHeadersMiddleware())
srv, _ := transporthttp.Register(ctx, "api", controller, log, handler, settings,
transporthttp.WithTLSPair(pair), transporthttp.WithMiddleware(chain))
On failure it writes a generic 401/403 with the credential redacted from logs.
Read the verified identity downstream with transporthttp.IdentityFromContext(ctx).
Add security headers¶
SecurityHeadersMiddleware sets a secure baseline (X-Content-Type-Options,
X-Frame-Options, Referrer-Policy, and optionally HSTS/CSP). Tune it with options:
sec := transporthttp.SecurityHeadersMiddleware(
transporthttp.WithHSTS(365*24*time.Hour, true, true),
transporthttp.WithContentSecurityPolicy("default-src 'self'"),
transporthttp.WithFrameOptions("DENY"),
)
Authenticate gRPC calls¶
transport/grpc mirrors the HTTP auth surface as an interceptor:
import (
transitgrpc "gitlab.com/phpboyscout/go/transit/grpc"
transportgrpc "gitlab.com/phpboyscout/go/transport/grpc"
)
ic, err := transportgrpc.AuthInterceptor(
transportgrpc.WithGRPCBearerVerifier(jwtVerifier),
transportgrpc.WithGRPCMethodSkipper(func(m string) bool { return m == "/admin.v1.Admin/Ping" }),
)
if err != nil {
return err
}
srv, err := transportgrpc.Register("rpc", controller, log, settings,
transportgrpc.WithTLSPair(pair),
transportgrpc.WithInterceptors(transitgrpc.NewInterceptorChain(ic)),
)
WithInterceptors is a registration option, so it goes to Register.
NewServer builds a bare server and returns an error if handed one. The skipper
adds exemptions on top of the health and reflection services, which the
interceptor already skips whatever you pass.
Require client certificates (mTLS)¶
Server-side mTLS is config-driven end to end through the
go/tls Pair: set ClientCAs (PEM CA files
client certificates must chain to) and ClientAuth (request,
verify-if-given, or require-verify) on the pair you pass to Register via
WithTLSPair, and pair it with the mTLS verifier on the auth
middleware/interceptor:
pair := gtls.Pair{
Enabled: true,
Cert: "/etc/certs/server.pem",
Key: "/etc/certs/server-key.pem",
ClientCAs: []string{"/etc/certs/client-ca.pem"},
ClientAuth: gtls.ClientAuthRequireVerify,
}
// HTTP: the handler sees non-empty r.TLS.VerifiedChains, and WithMTLSVerifier
// authenticates the request from them.
auth, _ := transporthttp.AuthMiddleware(transporthttp.WithMTLSVerifier(authn.NewMTLSVerifier()))
// gRPC: WithGRPCMTLSVerifier sees the verified chains via peer.FromContext.
ic, _ := transportgrpc.AuthInterceptor(transportgrpc.WithGRPCMTLSVerifier(authn.NewMTLSVerifier()))
Both managed paths honour a caller-supplied server TLS config
(transporthttp.WithServerTLSConfig / transportgrpc.WithServerTLSConfig) by
merging the pair into it rather than replacing it: the pair contributes its
certificate (and, on the gRPC listener, ALPN h2), while everything the caller
set (MinVersion, verification hooks, a client-certificate policy) survives.
Precedence: a config that already carries ClientCAs or a non-zero
ClientAuth wins over the pair's ClientCAs/ClientAuth fields.
Serve TLS from an in-memory certificate¶
A gtls.Pair describes a certificate by file path. When the certificate is
minted in memory and never written to disk (a self-signed fallback used when the
local CA cannot be installed into the system trust store, an in-memory
ACME/autocert certificate, a short-lived issuer, or a test), pass it with
WithServedCertificate and leave the pair disabled:
cert, _ := someIssuer.Mint() // *tls.Certificate, held in RAM
_, err := transporthttp.Register(
ctx, "studio", controller, logger, handler,
transporthttp.ServerSettings{Port: 8443},
transporthttp.WithServedCertificate(cert), // serve TLS from RAM, no pair
)
The server serves over TLS even though no pair is enabled, so there is no need to
persist a throwaway certificate to disk to satisfy the pair shape. The
certificate is appended to the server's TLS config, so a config supplied via
WithServerTLSConfig keeps its MinVersion, verification hooks, and any
client-certificate policy; the passed *tls.Certificate is not mutated. A
certificate placed directly on a WithServerTLSConfig is likewise served:
populated certificate material is treated as TLS intent.
Expose a gRPC service over REST with the gateway¶
transport/gateway wraps a gRPC connection in a grpc-gateway http.Handler. Give it a
connection (dial the gRPC server with go/grpcclient
or transport/grpc.DialLocal) and your generated registration function:
import transportgateway "gitlab.com/phpboyscout/go/transport/gateway"
conn, _ := transportgrpc.DialLocal(grpcSettings, grpcPair)
handler, err := transportgateway.New(ctx, conn,
func(ctx context.Context, mux *runtime.ServeMux, c *grpc.ClientConn) error {
return widgetv1.RegisterWidgetServiceHandler(ctx, mux, c)
},
transportgateway.WithMiddleware(chain), // optional transit Chain over the REST handler
)
The gateway handler is a plain http.Handler, so serve it through transport/http like
any other. The whole stack composes.
When the gRPC server requires client certificates, WithDialOptions is the
escape hatch for the gateway's outbound dial: pass
grpc.WithTransportCredentials(credentials.NewTLS(cfg)) with a client
*tls.Config carrying the gateway's client certificate and the CA pool that
signed the server's certificate. An explicit transport-credentials option
overrides the automatically selected transport security.
Where the full option lists live¶
This page composes the pieces; the exhaustive tables (every option, its default, and what happens when a value is wrong) are in the reference:
- HTTP server reference:
AuthMiddlewareoptions, credential precedence, the exact 401/403 responses, and the security-header defaults. - gRPC server reference:
AuthInterceptoroptions, and which methods skip authentication whatever you configure. - TLS settings reference: the
gtls.Pairfields, theClientAuthmodes, and which TLS setting wins when two disagree. - Gateway reference: what
NewandRegistereach own, including who closes the gRPC connection.
Two constraints that bite here and are stated in full under
What transport does not do: gRPC reflection
cannot be authenticated, and an enabled TLS pair must not be combined with your
own grpc.Creds.