samladapter

package
v0.0.4 Latest Latest
Warning

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

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

Documentation

Overview

Package samladapter provides a hardened SAML 2.0 service-provider implementation of the credbound.SSOProvider port, so hosts never hand-roll XML signature validation — historically the most dangerous part of SAML. The protocol exchange and XML-DSig verification are delegated to github.com/crewjam/saml; the adapter wraps them in credbound's ceremony contract and tightens the defaults.

Responsibilities

The adapter owns the protocol exchange only: building the AuthnRequest for the IdP's HTTP-Redirect binding (signed when an SP key is configured) and validating the posted response against the IdP metadata — signature, issuer, audience, destination, recipient, validity window, InResponseTo, and exactly one assertion. Credbound keeps everything else: the sealed continuation carrying the adapter's opaque session, ceremony TTL, identity linking, persistence, audit, and revocation.

Registration

Register a provider by wiring it into credbound.Config.SSOProviders. Static metadata is the primary path — paste the IdP's metadata document into the deployment so no network fetch ever happens:

metadataXML, err := os.ReadFile("idp-metadata.xml")
if err != nil {
	log.Fatal(err)
}
provider, err := samladapter.New(samladapter.Config{
	ConfigurationID: "0198b463-51a2-7cde-8000-0123456789ab", // UUIDv7 chosen by the host
	MetadataXML:     metadataXML,
	SPEntityID:      "https://app.example.com/saml/metadata",
	ACSURL:          "https://app.example.com/saml/acs",
})
if err != nil {
	log.Fatal(err)
}
manager, err := credbound.New(credbound.Config{
	Store:        store,
	Passwords:    hasher,
	SecretKey:    secretKey,
	SSOProviders: []credbound.SSOProvider{provider},
})

MetadataURL exists for IdPs that rotate signing certificates too often to redeploy: it is fetched lazily over HTTPS with a 10-second timeout, cached, and re-fetched once the metadata TTL elapses (Config.MetadataRefreshInterval, 12 hours by default), so a rotated or revoked IdP signing certificate is picked up without a redeploy; a failed refresh keeps serving the last good document.

Callback handling

The host's ACS endpoint (the HTTP-POST handler at ACSURL) forwards the provider response verbatim to credbound's FinishSSO. The adapter accepts either the raw base64 SAMLResponse form value or the full application/x-www-form-urlencoded request body:

func acs(w http.ResponseWriter, r *http.Request) {
	continuation := readContinuationCookie(r)
	body, _ := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
	auth, err := manager.FinishSSO(r.Context(), continuation, body)
	// ...
}

Subject and NameID policy

The NameID becomes the credbound subject — half of the stable link key — so it must be durable. The adapter requests the persistent format and accepts persistent or unspecified NameIDs; a transient NameID changes per ceremony and is rejected unless Config.AllowTransientNameID is set for IdPs that mislabel stable identifiers. Email is read from the standard attributes (mail, email, urn:oid:0.9.2342.19200300.100.1.3, and the WS-Fed emailaddress claim URI) but dropped by default: SAML carries no email_verified equivalent, so an IdP that lets a subject influence its outbound attributes could assert someone else's address. Set Config.TrustAssertedEmail for an IdP whose email attribute is authoritative; only then is a well-formed value forwarded as verified. A malformed or oversized value is dropped, never fatal — credbound keys SSO identities on issuer and subject, never on email.

Step-up limitation

credbound's step-up ceremonies set SSORequest.ForceReauthentication, which the adapter maps to ForceAuthn="true" on the AuthnRequest. SAML has no auth_time equivalent that the service provider can verify as strictly as OIDC's, so honoring ForceAuthn depends on the identity provider: the adapter cannot prove the IdP actually re-ran its authentication policy. Hosts with hard step-up requirements should prefer an OIDC provider (ssoadapter) for step-up flows.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrRequestIDMismatch reports that the response's InResponseTo does not
	// match the request id issued in Begin — an unsolicited or replayed
	// response.
	ErrRequestIDMismatch = errors.New("samladapter: InResponseTo does not match the ceremony request id")
	// ErrTransientNameID reports that the IdP asserted a transient NameID,
	// which cannot serve as a stable link key. Set
	// Config.AllowTransientNameID to accept it anyway.
	ErrTransientNameID = errors.New("samladapter: transient NameID rejected; set AllowTransientNameID to accept it")
)

Sentinel errors for the verification failures hosts most often want to distinguish in logs. Credbound maps every Finish error to ErrInvalidCredentials before it reaches the end user.

Functions

This section is empty.

Types

type Config

type Config struct {
	// ConfigurationID is the host-chosen UUIDv7 under which credbound
	// indexes this provider and its linked identities. Required.
	ConfigurationID credbound.UUID
	// MetadataXML is the IdP metadata document (an EntityDescriptor, or an
	// EntitiesDescriptor containing one IdP role). Static metadata is the
	// primary, recommended path: it is parsed eagerly so misconfiguration
	// surfaces at construction, and it never makes the adapter reach out to
	// the network. Exactly one of MetadataXML and MetadataURL must be set.
	MetadataXML []byte
	// MetadataURL is fetched lazily on first use and then re-fetched every
	// MetadataRefreshInterval, with a 10-second timeout and an SSRF-hardened
	// fetch: HTTPS only, every address the host resolves to must be publicly
	// routable, redirects are refused, and the connection is pinned to a
	// vetted address so a DNS rebind between resolution and dial cannot
	// steer the fetch into an internal network. Loopback hosts named
	// literally in the URL (localhost, 127.0.0.1, ::1) are exempt for
	// development and may also use plain HTTP. A failed refresh keeps
	// serving the last good metadata. Prefer MetadataXML; use MetadataURL
	// when the IdP rotates certificates too often to redeploy with fresh
	// static metadata.
	MetadataURL string
	// MetadataRefreshInterval bounds how long a fetched MetadataURL document is
	// cached before it is re-fetched, so a rotated or revoked IdP signing
	// certificate is picked up without a redeploy. Zero uses a default of 12
	// hours. Ignored for static MetadataXML.
	MetadataRefreshInterval time.Duration
	// SPEntityID is this service provider's entity ID: the value the IdP
	// must put in the assertion's AudienceRestriction and the Issuer of the
	// AuthnRequests the adapter emits. Required.
	SPEntityID string
	// ACSURL is the host's assertion consumer service URL — the endpoint
	// whose handler forwards the posted response to credbound's FinishSSO.
	// The assertion's Destination and SubjectConfirmation Recipient must
	// match it. Required. HTTPS is mandatory except for loopback hosts.
	ACSURL string
	// Certificate is the SP signing certificate, paired with Key. Optional;
	// when both are set, HTTP-Redirect AuthnRequests are signed (SigAlg and
	// Signature query parameters, SHA-256). Set both or neither.
	Certificate *x509.Certificate
	// Key is the SP signing key: an *rsa.PrivateKey or *ecdsa.PrivateKey.
	Key crypto.Signer
	// AllowTransientNameID accepts transient-format NameIDs as the subject.
	// Off by default because a transient NameID changes per ceremony and
	// therefore cannot key a durable credbound SSO identity; enable it only
	// for IdPs that mislabel stable identifiers as transient.
	AllowTransientNameID bool
	// TrustAssertedEmail forwards the asserted email attribute to credbound as
	// verified. Off by default: SAML carries no email_verified equivalent, so
	// an IdP that lets a subject influence its outbound attributes (self-service
	// profiles, loosely-mapped directories, guest accounts) could otherwise
	// assert someone else's address and drive JIT provisioning to squat it.
	// Enable it only for an IdP whose email attribute is authoritative for the
	// subject. When off, the email is dropped and credbound keys the identity
	// on issuer and subject alone.
	TrustAssertedEmail bool
	// HTTPClient is used only for the one-shot MetadataURL fetch. Defaults
	// to a client with a 10-second timeout; a supplied client without a
	// timeout is shallow-copied and given the default. The Resolver vetting
	// and the redirect refusal apply to supplied clients too, but the
	// connection pinning that defeats DNS rebinding requires the default
	// client: a host that supplies its own transport takes over that part
	// of the SSRF posture.
	HTTPClient *http.Client
	// Resolver vets MetadataURL hosts before dialing: every address the
	// host resolves to must be publicly routable, or the fetch is refused.
	// Defaults to net.DefaultResolver. Override it only in tests.
	Resolver Resolver
	// Clock supplies the current time for the adapter's own re-validation of
	// assertion conditions. Defaults to time.Now. Note that crewjam/saml
	// validates its timestamps with its package-level saml.TimeNow, which
	// this per-provider clock does not replace.
	Clock func() time.Time
}

Config describes one SAML identity provider registration.

type Provider

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

Provider is a SAML 2.0 service-provider implementation of credbound.SSOProvider backed by github.com/crewjam/saml for the protocol and XML-DSig validation. It is stateless across ceremonies: everything a Finish needs travels inside the opaque Session bytes that credbound seals into its continuation.

func New

func New(config Config) (*Provider, error)

New validates the configuration and returns a Provider ready to register in credbound.Config.SSOProviders. Static metadata is parsed eagerly; MetadataURL is fetched lazily on first use so construction does not require the IdP to be reachable.

func (*Provider) Begin

Begin implements credbound.SSOProvider. It builds an AuthnRequest for the IdP's HTTP-Redirect SSO endpoint (deflated, base64- and URL-encoded, and signed when an SP key is configured) and returns the request id as opaque Session bytes for credbound to seal into its continuation.

When SSORequest.ForceReauthentication is set the request carries ForceAuthn="true". SAML gives the SP no auth_time-equivalent to verify afterwards, so unlike the OIDC adapter this remains a request the IdP is trusted — not proven — to honor; see the package documentation.

func (*Provider) ConfigurationID

func (p *Provider) ConfigurationID() credbound.UUID

ConfigurationID implements credbound.SSOProvider.

func (*Provider) Finish

func (p *Provider) Finish(ctx context.Context, sessionBytes, response []byte) (credbound.SSOClaims, error)

Finish implements credbound.SSOProvider. sessionBytes is the Session issued by Begin (returned by credbound from its sealed continuation) and response is the payload the host's ACS handler received: either the raw base64 SAMLResponse form value or the full application/x-www-form-urlencoded request body — both are detected and handled.

Finish validates the response through crewjam/saml against the IdP metadata: XML signature, issuer, destination and recipient (the ACS URL), NotBefore/NotOnOrAfter with a small clock skew, and audience, which the adapter tightens to require an AudienceRestriction naming SPEntityID. The response and every SubjectConfirmation must carry an InResponseTo equal (constant-time) to the Session's request id, so unsolicited and IdP-initiated responses are rejected, and the response must contain exactly one assertion. The verified claims map Issuer to the IdP entity ID and Subject to the NameID, whose format must be persistent or unspecified (transient only with AllowTransientNameID). Errors never include assertion or response XML.

func (*Provider) Kind

Kind implements credbound.SSOProvider.

type Resolver

type Resolver interface {
	LookupIPAddr(context.Context, string) ([]net.IPAddr, error)
}

Resolver resolves host names for the SSRF guard on MetadataURL fetches; net.DefaultResolver satisfies it.

Jump to

Keyboard shortcuts

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