Documentation
¶
Overview ¶
Package syncauth is the one definition of how KySignOn signs a directory-sync event and how a product verifies it. KySignOn calls Sign; every receiver installs Middleware. Before this package the sender signed timestamp+"."+body and at least one receiver verified body alone, so the two sides could not agree, and a receiver that accepts a forged deprovision cannot tell it from a real one.
Scheme v1: HMAC-SHA256 over
"ky-sync-v1\n" + timestamp(RFC3339, UTC) + "\n" + eventType + "\n" + eventID + "\n" + hex(sha256(body))
carried in X-KySignOn-Signature as "v1=" + hex(mac), with X-KySignOn-Timestamp, X-KySignOn-Event-Type and X-KySignOn-Event-ID alongside. The HMAC key is the sync secret and travels nowhere: no Authorization header carries it. Event type and ID are bound so a captured event cannot be replayed as a different type, and the ID is what the receiver's replay guard remembers.
Index ¶
Constants ¶
const ( HeaderSignature = "X-KySignOn-Signature" HeaderTimestamp = "X-KySignOn-Timestamp" HeaderEventType = "X-KySignOn-Event-Type" HeaderEventID = "X-KySignOn-Event-ID" // DefaultWindow is how far a timestamp may sit from the receiver's clock. Homelab clocks // drift; five minutes is the usual bound and the error names which side is off. DefaultWindow = 5 * time.Minute // DefaultMaxBody bounds what a receiver reads before verifying. A directory event with // a few thousand users fits in far less. DefaultMaxBody = 4 << 20 // MinKeyBytes is the shortest sync secret accepted. KySignOn mints 32 random bytes. MinKeyBytes = 16 )
Variables ¶
var ( ErrNoSignature = errors.New("syncauth: no signature") ErrBadSignature = errors.New("syncauth: signature does not match") ErrBadTimestamp = errors.New("syncauth: timestamp missing or not RFC3339") ErrStale = errors.New("syncauth: timestamp outside the accepted window") ErrReplay = errors.New("syncauth: event already seen") // ErrReplayGuardFull means the in-memory guard holds max IDs all still inside the window // and cannot admit another without forgetting one it must remember. The receiver logs // that the guard is undersized; a persistent Replay backed by its processed-events table // is the fix for sustained volume. ErrReplayGuardFull = errors.New("syncauth: replay guard is full of events still inside the window") ErrMissingFields = errors.New("syncauth: event type and event id are required") ErrShortKey = fmt.Errorf("syncauth: key must be at least %d bytes", MinKeyBytes) ErrBodyTooLarge = errors.New("syncauth: body exceeds the limit") )
Functions ¶
func Middleware ¶
func Middleware(keyFn func(r *http.Request) ([]byte, error), o Options, maxBody int64, onReject func(r *http.Request, err error)) func(http.Handler) http.Handler
Middleware reads a bounded body, verifies it, and passes the request on with the same bytes replaced on r.Body so the handler decodes exactly what was signed. keyFn resolves the sync secret for this request (one per deployment, or per sender); an error from it is a 401 too, so an unconfigured receiver fails closed. Failures answer 401 with a fixed body; the reason goes only to onReject, for the receiver's log.
Types ¶
type Event ¶
Event is what a verified request says about itself.
func EventFromContext ¶
EventFromContext returns the verified Event Middleware stored.
type Headers ¶
Headers are the four headers a signed request carries.
func FromRequest ¶
FromRequest reads the headers off a request.
type Options ¶
type Options struct {
// Window bounds |now - timestamp|. Zero means DefaultWindow.
Window time.Duration
// Now is the receiver's clock; nil means time.Now.
Now func() time.Time
// Replay, when set, is asked whether the event ID was already accepted inside the
// window; a receiver with a table supplies its own, others use NewMemoryReplay.
Replay Replay
}
Options tunes Verify.
type Replay ¶
Replay remembers accepted event IDs. Check returns nil and records id when it is new, ErrReplay when it was already recorded, or another error when it cannot decide (a guard that is full, a table that is down); any error refuses the event. Implementations must be safe for concurrent use.
func NewMemoryReplay ¶
NewMemoryReplay returns a Replay that remembers up to max IDs for window. Use it when the receiver has no table of processed events; a restart forgets everything, which is the window's job to bound. Size max above the events one sender can emit in a window, or the guard refuses under load.