Skip to content

Commit 27ccd58

Browse files
authored
Merge pull request #81 from bucko13/poc-creation-time-caveat
Timeout Caveat Support
2 parents fa7df9e + 62f604b commit 27ccd58

File tree

11 files changed

+325
-5
lines changed

11 files changed

+325
-5
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@
1313

1414
/aperture
1515
cmd/aperture/aperture
16+
17+
# misc
18+
.vscode

aperture.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@ func createProxy(cfg *Config, challenger *LndChallenger,
662662
Challenger: challenger,
663663
Secrets: newSecretStore(etcdClient),
664664
ServiceLimiter: newStaticServiceLimiter(cfg.Services),
665+
Now: time.Now,
665666
})
666667
authenticator := auth.NewLsatAuthenticator(minter, challenger)
667668

lsat/satisfier.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package lsat
22

33
import (
44
"fmt"
5+
"strconv"
56
"strings"
7+
"time"
68
)
79

810
// Satisfier provides a generic interface to satisfy a caveat based on its
@@ -79,7 +81,9 @@ func NewServicesSatisfier(targetService string) Satisfier {
7981

8082
// NewCapabilitiesSatisfier implements a satisfier to determine whether the
8183
// target capability for a service is authorized for a given LSAT.
82-
func NewCapabilitiesSatisfier(service string, targetCapability string) Satisfier {
84+
func NewCapabilitiesSatisfier(service string,
85+
targetCapability string) Satisfier {
86+
8387
return Satisfier{
8488
Condition: service + CondCapabilitiesSuffix,
8589
SatisfyPrevious: func(prev, cur Caveat) error {
@@ -115,3 +119,62 @@ func NewCapabilitiesSatisfier(service string, targetCapability string) Satisfier
115119
},
116120
}
117121
}
122+
123+
// NewTimeoutSatisfier checks if an LSAT is expired or not. The Satisfier takes
124+
// a service name to set as the condition prefix and currentTimestamp to
125+
// compare against the expiration(s) in the caveats. The expiration time is
126+
// retrieved from the caveat values themselves. The satisfier will also make
127+
// sure that each subsequent caveat of the same condition only has increasingly
128+
// strict expirations.
129+
func NewTimeoutSatisfier(service string, now func() time.Time) Satisfier {
130+
return Satisfier{
131+
Condition: service + CondTimeoutSuffix,
132+
SatisfyPrevious: func(prev, cur Caveat) error {
133+
prevValue, err := strconv.ParseInt(prev.Value, 10, 64)
134+
if err != nil {
135+
return fmt.Errorf("error parsing previous "+
136+
"caveat value: %w", err)
137+
}
138+
139+
currValue, err := strconv.ParseInt(cur.Value, 10, 64)
140+
if err != nil {
141+
return fmt.Errorf("error parsing caveat "+
142+
"value: %w", err)
143+
}
144+
145+
prevTime := time.Unix(prevValue, 0)
146+
currTime := time.Unix(currValue, 0)
147+
148+
// Satisfier should fail if a previous timestamp in the
149+
// list is earlier than ones after it b/c that means
150+
// they are getting more permissive.
151+
if prevTime.Before(currTime) {
152+
return fmt.Errorf("%s caveat violates "+
153+
"increasing restrictiveness",
154+
service+CondTimeoutSuffix)
155+
}
156+
157+
return nil
158+
},
159+
SatisfyFinal: func(c Caveat) error {
160+
expirationTimestamp, err := strconv.ParseInt(
161+
c.Value, 10, 64,
162+
)
163+
if err != nil {
164+
return fmt.Errorf("caveat value not a valid "+
165+
"integer: %v", err)
166+
}
167+
168+
expirationTime := time.Unix(expirationTimestamp, 0)
169+
170+
// Make sure that the final relevant caveat is not
171+
// passed the current date/time.
172+
if now().Before(expirationTime) {
173+
return nil
174+
}
175+
176+
return fmt.Errorf("not authorized to access " +
177+
"service. LSAT has expired")
178+
},
179+
}
180+
}

lsat/satisfier_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package lsat
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// TestTimeoutSatisfier tests that the Timeout Satisfier implementation behaves
12+
// as expected and correctly accepts or rejects calls based on if the
13+
// timeout has been reached or not.
14+
func TestTimeoutSatisfier(t *testing.T) {
15+
t.Parallel()
16+
17+
now := int64(0)
18+
19+
var tests = []struct {
20+
name string
21+
timeouts []int64
22+
expectFinalErr bool
23+
expectPrevErr bool
24+
}{
25+
{
26+
name: "current time is before expiration",
27+
timeouts: []int64{now + 1000},
28+
},
29+
{
30+
name: "time passed is greater than " +
31+
"expiration",
32+
timeouts: []int64{now - 1000},
33+
expectFinalErr: true,
34+
},
35+
{
36+
name: "successive caveats are increasingly " +
37+
"restrictive and not yet expired",
38+
timeouts: []int64{now + 1000, now + 500},
39+
},
40+
{
41+
name: "latter caveat is less restrictive " +
42+
"then previous",
43+
timeouts: []int64{now + 500, now + 1000},
44+
expectPrevErr: true,
45+
},
46+
}
47+
48+
var (
49+
service = "restricted"
50+
condition = service + CondTimeoutSuffix
51+
satisfier = NewTimeoutSatisfier(service, func() time.Time {
52+
return time.Unix(now, 0)
53+
})
54+
)
55+
56+
for _, test := range tests {
57+
test := test
58+
t.Run(test.name, func(t *testing.T) {
59+
var prev *Caveat
60+
for _, timeout := range test.timeouts {
61+
caveat := NewCaveat(
62+
condition, fmt.Sprintf("%d", timeout),
63+
)
64+
65+
if prev != nil {
66+
err := satisfier.SatisfyPrevious(
67+
*prev, caveat,
68+
)
69+
if test.expectPrevErr {
70+
require.Error(t, err)
71+
} else {
72+
require.NoError(t, err)
73+
}
74+
}
75+
76+
err := satisfier.SatisfyFinal(caveat)
77+
if test.expectFinalErr {
78+
require.Error(t, err)
79+
} else {
80+
require.NoError(t, err)
81+
}
82+
83+
prev = &caveat
84+
}
85+
})
86+
}
87+
}

lsat/service.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strconv"
77
"strings"
8+
"time"
89
)
910

1011
const (
@@ -15,6 +16,10 @@ const (
1516
// capabilities caveat. For example, the condition of a capabilities
1617
// caveat for a service named `loop` would be `loop_capabilities`.
1718
CondCapabilitiesSuffix = "_capabilities"
19+
20+
// CondTimeoutSuffix is the condition suffix used for a service's
21+
// timeout caveat.
22+
CondTimeoutSuffix = "_valid_until"
1823
)
1924

2025
var (
@@ -129,3 +134,19 @@ func NewCapabilitiesCaveat(serviceName string, capabilities string) Caveat {
129134
Value: capabilities,
130135
}
131136
}
137+
138+
// NewTimeoutCaveat creates a new caveat that will result in a macaroon being
139+
// valid for numSeconds after the current time.
140+
func NewTimeoutCaveat(serviceName string, numSeconds int64,
141+
now func() time.Time) Caveat {
142+
143+
var (
144+
macaroonTimeout = time.Duration(numSeconds) * time.Second
145+
requestTimeout = now().Add(macaroonTimeout)
146+
)
147+
148+
return Caveat{
149+
Condition: serviceName + CondTimeoutSuffix,
150+
Value: strconv.FormatInt(requestTimeout.Unix(), 10),
151+
}
152+
}

mint/mint.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"crypto/sha256"
88
"errors"
99
"fmt"
10+
"time"
1011

1112
"github.com/lightninglabs/aperture/lsat"
1213
"github.com/lightningnetwork/lnd/lntypes"
@@ -60,6 +61,10 @@ type ServiceLimiter interface {
6061
// enforces additional constraints on a particular service/service
6162
// capability.
6263
ServiceConstraints(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
64+
65+
// ServiceTimeouts returns the timeout caveat for each service. This
66+
// will determine if and when service access can expire.
67+
ServiceTimeouts(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
6368
}
6469

6570
// Config packages all of the required dependencies to instantiate a new LSAT
@@ -76,6 +81,9 @@ type Config struct {
7681
// ServiceLimiter provides us with how we should limit a new LSAT based
7782
// on its target services.
7883
ServiceLimiter ServiceLimiter
84+
85+
// Now returns the current time.
86+
Now func() time.Time
7987
}
8088

8189
// Mint is an entity that is able to mint and verify LSATs for a set of
@@ -210,10 +218,15 @@ func (m *Mint) caveatsForServices(ctx context.Context,
210218
if err != nil {
211219
return nil, err
212220
}
221+
timeouts, err := m.cfg.ServiceLimiter.ServiceTimeouts(ctx, services...)
222+
if err != nil {
223+
return nil, err
224+
}
213225

214226
caveats := []lsat.Caveat{servicesCaveat}
215227
caveats = append(caveats, capabilities...)
216228
caveats = append(caveats, constraints...)
229+
caveats = append(caveats, timeouts...)
217230
return caveats, nil
218231
}
219232

@@ -269,6 +282,8 @@ func (m *Mint) VerifyLSAT(ctx context.Context, params *VerificationParams) error
269282
caveats = append(caveats, caveat)
270283
}
271284
return lsat.VerifyCaveats(
272-
caveats, lsat.NewServicesSatisfier(params.TargetService),
285+
caveats,
286+
lsat.NewServicesSatisfier(params.TargetService),
287+
lsat.NewTimeoutSatisfier(params.TargetService, m.cfg.Now),
273288
)
274289
}

0 commit comments

Comments
 (0)