carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package moves
2
3import (
4 "errors"
5 "fmt"
6 "math"
7 "time"
8)
9
10// SignatureLife is how long S3 accepts a SigV4 signature (15 minutes,
11// verified §15 step 8). A staging writer may sign only while its lease is
12// safe for that whole life plus the clock skew (§7.3, model 14, 18).
13const SignatureLife = 15 * time.Minute
14
15// StagingLeaseTTL is the staging-work lease's term (D3). The ordinary
16// lease.LeaseTTL is 15 minutes, which can never leave SignatureLife plus
17// the skew in front of SafeUntil; 45 minutes gives every renewal cycle
18// room, and the renewer keeps the bound ahead while the lane works.
19const StagingLeaseTTL = 45 * time.Minute
20
21// DefaultSkew is the clock-skew allowance the guard subtracts.
22const DefaultSkew = time.Minute
23
24// ErrLeaseUnsafe is the guard's refusal: the staging lease is too close to
25// its end (or already lost) for another request to be signed.
26var ErrLeaseUnsafe = errors.New("moves: staging lease too close to its end to sign another request")
27
28// StagingLeasePrefix is the claim's shared staging-work lease (claim C3,
29// §7.3 D3): in the deployment bucket, keyed by target region and host, so
30// a source that cannot read the target's lock.json still excludes other
31// writers of the same lane.
32func StagingLeasePrefix(targetRegion, host string) string {
33 return "control/staging-leases/" + targetRegion + "/" + host
34}
35
36// ErrInvalidSkew is a negative Guarded.Skew: it would shrink the window
37// the guard exists to keep (a -15 minute skew admits a signature that
38// outlives the lease), so it is refused rather than honoured.
39var ErrInvalidSkew = errors.New("moves: staging guard skew must not be negative")
40
41// SafeUntilGuard is the one method of *lease.Held the guard reads.
42type SafeUntilGuard interface{ SafeUntil() (time.Time, error) }
43
44// Guarded wraps every staging request: the check runs immediately before
45// fn signs, and fn signs AT THE CHECK'S TIMESTAMP, which Before hands it.
46// A request that cannot complete within the signature's life before the
47// lease ends is dropped; a request signed at the check and sent after any
48// pause carries a date S3 stops accepting exactly when the guard's bound
49// said it must. fn must issue exactly one request through a client that
50// signs once, at signAt, and never retries (lease.StagingMirror's At
51// methods); a caller that wants a retry calls Before again.
52type Guarded struct {
53 Lease SafeUntilGuard
54 Now func() time.Time
55 Skew time.Duration // zero means DefaultSkew
56}
57
58// Before refuses with ErrLeaseUnsafe unless the lease stays safe for the
59// signature's whole life plus the skew from now, where now is read once
60// and handed to fn as the signing time. lease.Held.SafeUntil already
61// subtracts the renewal margin; Before subtracts the signature's life.
62func (g Guarded) Before(fn func(signAt time.Time) error) error {
63 skew := g.Skew
64 if skew < 0 {
65 return ErrInvalidSkew
66 }
67 if skew == 0 {
68 skew = DefaultSkew
69 }
70 safe, err := g.Lease.SafeUntil()
71 if err != nil {
72 return fmt.Errorf("%w: %v", ErrLeaseUnsafe, err)
73 }
74 // A skew so large that SignatureLife+skew would overflow can never be
75 // satisfied: refuse rather than wrap into a pass.
76 if skew > math.MaxInt64-SignatureLife {
77 return ErrLeaseUnsafe
78 }
79 now := g.Now()
80 // safe.Sub saturates, so no sum of times can overflow; the comparison
81 // is the same as now+SignatureLife+skew being after safe.
82 if safe.Sub(now) < SignatureLife+skew {
83 return ErrLeaseUnsafe
84 }
85 return fn(now)
86}
87