Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timeout Caveat Support #81

Merged
merged 1 commit into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
bucko13 marked this conversation as resolved.
Show resolved Hide resolved
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
},
}
}

bucko13 marked this conversation as resolved.
Show resolved Hide resolved
// NewTimeoutSatisfier checks if an LSAT is expired or not. The Satisfier takes
ellemouton marked this conversation as resolved.
Show resolved Hide resolved
// 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{
bucko13 marked this conversation as resolved.
Show resolved Hide resolved
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) {
bucko13 marked this conversation as resolved.
Show resolved Hide resolved
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