Skip to content

Commit

Permalink
aperture: adds timeout option for services for relative expiration
Browse files Browse the repository at this point in the history
  • Loading branch information
bucko13 committed Mar 31, 2023
1 parent dd485d9 commit 2ba9e37
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 8 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
2 changes: 1 addition & 1 deletion aperture.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ func createProxy(cfg *Config, challenger *LndChallenger,
Challenger: challenger,
Secrets: newSecretStore(etcdClient),
ServiceLimiter: newStaticServiceLimiter(cfg.Services),
})
}, &mint.GetTime{})
authenticator := auth.NewLsatAuthenticator(minter, challenger)

// By default the static file server only returns 404 answers for
Expand Down
67 changes: 66 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,64 @@ 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 {
// make sure that the final relevant caveat is not
// passed the current date/time
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)

if now().Before(expirationTime) {
return nil
}

return fmt.Errorf(
"not authorized to access service. LSAT has " +
"expired",
)
},
}
}
86 changes: 86 additions & 0 deletions lsat/satisfier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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 {
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),
}
}
18 changes: 16 additions & 2 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import (
"crypto/sha256"
"errors"
"fmt"

"github.com/lightninglabs/aperture/lsat"
"github.com/lightningnetwork/lnd/lntypes"
"gopkg.in/macaroon.v2"
"time"
)

var (
Expand Down Expand Up @@ -60,6 +60,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 +80,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 +217,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 +281,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 2ba9e37

Please sign in to comment.