Skip to content

Commit

Permalink
Merge pull request #81 from bucko13/poc-creation-time-caveat
Browse files Browse the repository at this point in the history
Timeout Caveat Support
  • Loading branch information
guggero authored Apr 26, 2023
2 parents fa7df9e + 62f604b commit 27ccd58
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@

/aperture
cmd/aperture/aperture

# misc
.vscode
1 change: 1 addition & 0 deletions aperture.go
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ func createProxy(cfg *Config, challenger *LndChallenger,
Challenger: challenger,
Secrets: newSecretStore(etcdClient),
ServiceLimiter: newStaticServiceLimiter(cfg.Services),
Now: time.Now,
})
authenticator := auth.NewLsatAuthenticator(minter, challenger)

Expand Down
65 changes: 64 additions & 1 deletion lsat/satisfier.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package lsat

import (
"fmt"
"strconv"
"strings"
"time"
)

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

// NewCapabilitiesSatisfier implements a satisfier to determine whether the
// target capability for a service is authorized for a given LSAT.
func NewCapabilitiesSatisfier(service string, targetCapability string) Satisfier {
func NewCapabilitiesSatisfier(service string,
targetCapability string) Satisfier {

return Satisfier{
Condition: service + CondCapabilitiesSuffix,
SatisfyPrevious: func(prev, cur Caveat) error {
Expand Down Expand Up @@ -115,3 +119,62 @@ func NewCapabilitiesSatisfier(service string, targetCapability string) Satisfier
},
}
}

// NewTimeoutSatisfier checks if an LSAT is expired or not. The Satisfier takes
// a service name to set as the condition prefix and currentTimestamp to
// compare against the expiration(s) in the caveats. The expiration time is
// retrieved from the caveat values themselves. The satisfier will also make
// sure that each subsequent caveat of the same condition only has increasingly
// strict expirations.
func NewTimeoutSatisfier(service string, now func() time.Time) Satisfier {
return Satisfier{
Condition: service + CondTimeoutSuffix,
SatisfyPrevious: func(prev, cur Caveat) error {
prevValue, err := strconv.ParseInt(prev.Value, 10, 64)
if err != nil {
return fmt.Errorf("error parsing previous "+
"caveat value: %w", err)
}

currValue, err := strconv.ParseInt(cur.Value, 10, 64)
if err != nil {
return fmt.Errorf("error parsing caveat "+
"value: %w", err)
}

prevTime := time.Unix(prevValue, 0)
currTime := time.Unix(currValue, 0)

// Satisfier should fail if a previous timestamp in the
// list is earlier than ones after it b/c that means
// they are getting more permissive.
if prevTime.Before(currTime) {
return fmt.Errorf("%s caveat violates "+
"increasing restrictiveness",
service+CondTimeoutSuffix)
}

return nil
},
SatisfyFinal: func(c Caveat) error {
expirationTimestamp, err := strconv.ParseInt(
c.Value, 10, 64,
)
if err != nil {
return fmt.Errorf("caveat value not a valid "+
"integer: %v", err)
}

expirationTime := time.Unix(expirationTimestamp, 0)

// Make sure that the final relevant caveat is not
// passed the current date/time.
if now().Before(expirationTime) {
return nil
}

return fmt.Errorf("not authorized to access " +
"service. LSAT has expired")
},
}
}
87 changes: 87 additions & 0 deletions lsat/satisfier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package lsat

import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/require"
)

// TestTimeoutSatisfier tests that the Timeout Satisfier implementation behaves
// as expected and correctly accepts or rejects calls based on if the
// timeout has been reached or not.
func TestTimeoutSatisfier(t *testing.T) {
t.Parallel()

now := int64(0)

var tests = []struct {
name string
timeouts []int64
expectFinalErr bool
expectPrevErr bool
}{
{
name: "current time is before expiration",
timeouts: []int64{now + 1000},
},
{
name: "time passed is greater than " +
"expiration",
timeouts: []int64{now - 1000},
expectFinalErr: true,
},
{
name: "successive caveats are increasingly " +
"restrictive and not yet expired",
timeouts: []int64{now + 1000, now + 500},
},
{
name: "latter caveat is less restrictive " +
"then previous",
timeouts: []int64{now + 500, now + 1000},
expectPrevErr: true,
},
}

var (
service = "restricted"
condition = service + CondTimeoutSuffix
satisfier = NewTimeoutSatisfier(service, func() time.Time {
return time.Unix(now, 0)
})
)

for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
var prev *Caveat
for _, timeout := range test.timeouts {
caveat := NewCaveat(
condition, fmt.Sprintf("%d", timeout),
)

if prev != nil {
err := satisfier.SatisfyPrevious(
*prev, caveat,
)
if test.expectPrevErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}

err := satisfier.SatisfyFinal(caveat)
if test.expectFinalErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}

prev = &caveat
}
})
}
}
21 changes: 21 additions & 0 deletions lsat/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strconv"
"strings"
"time"
)

const (
Expand All @@ -15,6 +16,10 @@ const (
// capabilities caveat. For example, the condition of a capabilities
// caveat for a service named `loop` would be `loop_capabilities`.
CondCapabilitiesSuffix = "_capabilities"

// CondTimeoutSuffix is the condition suffix used for a service's
// timeout caveat.
CondTimeoutSuffix = "_valid_until"
)

var (
Expand Down Expand Up @@ -129,3 +134,19 @@ func NewCapabilitiesCaveat(serviceName string, capabilities string) Caveat {
Value: capabilities,
}
}

// NewTimeoutCaveat creates a new caveat that will result in a macaroon being
// valid for numSeconds after the current time.
func NewTimeoutCaveat(serviceName string, numSeconds int64,
now func() time.Time) Caveat {

var (
macaroonTimeout = time.Duration(numSeconds) * time.Second
requestTimeout = now().Add(macaroonTimeout)
)

return Caveat{
Condition: serviceName + CondTimeoutSuffix,
Value: strconv.FormatInt(requestTimeout.Unix(), 10),
}
}
17 changes: 16 additions & 1 deletion mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/sha256"
"errors"
"fmt"
"time"

"github.com/lightninglabs/aperture/lsat"
"github.com/lightningnetwork/lnd/lntypes"
Expand Down Expand Up @@ -60,6 +61,10 @@ type ServiceLimiter interface {
// enforces additional constraints on a particular service/service
// capability.
ServiceConstraints(context.Context, ...lsat.Service) ([]lsat.Caveat, error)

// ServiceTimeouts returns the timeout caveat for each service. This
// will determine if and when service access can expire.
ServiceTimeouts(context.Context, ...lsat.Service) ([]lsat.Caveat, error)
}

// Config packages all of the required dependencies to instantiate a new LSAT
Expand All @@ -76,6 +81,9 @@ type Config struct {
// ServiceLimiter provides us with how we should limit a new LSAT based
// on its target services.
ServiceLimiter ServiceLimiter

// Now returns the current time.
Now func() time.Time
}

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

caveats := []lsat.Caveat{servicesCaveat}
caveats = append(caveats, capabilities...)
caveats = append(caveats, constraints...)
caveats = append(caveats, timeouts...)
return caveats, nil
}

Expand Down Expand Up @@ -269,6 +282,8 @@ func (m *Mint) VerifyLSAT(ctx context.Context, params *VerificationParams) error
caveats = append(caveats, caveat)
}
return lsat.VerifyCaveats(
caveats, lsat.NewServicesSatisfier(params.TargetService),
caveats,
lsat.NewServicesSatisfier(params.TargetService),
lsat.NewTimeoutSatisfier(params.TargetService, m.cfg.Now),
)
}
Loading

0 comments on commit 27ccd58

Please sign in to comment.