Skip to content

Commit

Permalink
Merge pull request lightninglabs#135 from starius/s-lsat-l402
Browse files Browse the repository at this point in the history
multi: change LSAT name to L402
  • Loading branch information
guggero authored Apr 23, 2024
2 parents b1bad8a + 683093c commit 9463266
Show file tree
Hide file tree
Showing 40 changed files with 625 additions and 442 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ non-custodial on/off ramp for the Lightning Network.

Aperture is a HTTP 402 reverse proxy that supports proxying requests for gRPC
(HTTP/2) and REST (HTTP/1 and HTTP/2) backends using the [L402 Protocol
Standard](https://lsat.tech/). L402 is short for: the Lightning HTTP 402
Standard][l402]. L402 is short for: the Lightning HTTP 402
protocol. L402 combines HTTP 402, macaroons, and the Lightning Network to
create a new standard for authentication and paid services on the web.

Expand All @@ -23,6 +23,8 @@ novel constructs such as automated tier upgrades. In another light, this can be
viewed as a global HTTP 402 reverse proxy at the load balancing level for web
services and APIs.

[l402]: https://github.com/lightninglabs/L402

## Installation / Setup

**lnd**
Expand Down
6 changes: 3 additions & 3 deletions aperture.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (

const (
// topLevelKey is the top level key for an etcd cluster where we'll
// store all LSAT proxy related data.
// store all L402 proxy related data.
topLevelKey = "lsat/proxy"

// etcdKeyDelimeter is the delimeter we'll use for all etcd keys to
Expand Down Expand Up @@ -311,7 +311,7 @@ func (a *Aperture) Start(errChan chan error) error {
authCfg := a.cfg.Authenticator
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
return &lnrpc.Invoice{
Memo: "LSAT",
Memo: "L402",
Value: price,
}, nil
}
Expand Down Expand Up @@ -819,7 +819,7 @@ func createProxy(cfg *Config, challenger challenger.Challenger,
ServiceLimiter: newStaticServiceLimiter(cfg.Services),
Now: time.Now,
})
authenticator := auth.NewLsatAuthenticator(minter, challenger)
authenticator := auth.NewL402Authenticator(minter, challenger)

// By default the static file server only returns 404 answers for
// security reasons. Serving files from the staticRoot directory has to
Expand Down
16 changes: 8 additions & 8 deletions aperturedb/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"fmt"

"github.com/lightninglabs/aperture/aperturedb/sqlc"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/aperture/mint"
"github.com/lightningnetwork/lnd/clock"
)
Expand Down Expand Up @@ -81,11 +81,11 @@ func NewSecretsStore(db BatchedSecretsDB) *SecretsStore {
// NewSecret creates a new cryptographically random secret which is
// keyed by the given hash.
func (s *SecretsStore) NewSecret(ctx context.Context,
hash [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
hash [sha256.Size]byte) ([l402.SecretSize]byte, error) {

var secret [lsat.SecretSize]byte
var secret [l402.SecretSize]byte
if _, err := rand.Read(secret[:]); err != nil {
return [lsat.SecretSize]byte{}, err
return [l402.SecretSize]byte{}, err
}

var writeTxOpts SecretsDBTxOptions
Expand All @@ -103,7 +103,7 @@ func (s *SecretsStore) NewSecret(ctx context.Context,
})

if err != nil {
return [lsat.SecretSize]byte{}, fmt.Errorf("unable to insert "+
return [l402.SecretSize]byte{}, fmt.Errorf("unable to insert "+
"new secret for hash(%x): %w", hash, err)
}

Expand All @@ -114,9 +114,9 @@ func (s *SecretsStore) NewSecret(ctx context.Context,
// corresponds to the given hash. If there is no secret, then
// ErrSecretNotFound is returned.
func (s *SecretsStore) GetSecret(ctx context.Context,
hash [sha256.Size]byte) ([lsat.SecretSize]byte, error) {
hash [sha256.Size]byte) ([l402.SecretSize]byte, error) {

var secret [lsat.SecretSize]byte
var secret [l402.SecretSize]byte
readOpts := NewSecretsDBReadTx()
err := s.db.ExecTx(ctx, &readOpts, func(db SecretsDB) error {
secretRow, err := db.GetSecretByHash(ctx, hash[:])
Expand All @@ -134,7 +134,7 @@ func (s *SecretsStore) GetSecret(ctx context.Context,
})

if err != nil {
return [lsat.SecretSize]byte{}, fmt.Errorf("unable to get "+
return [l402.SecretSize]byte{}, fmt.Errorf("unable to get "+
"secret for hash(%x): %w", hash, err)
}

Expand Down
67 changes: 43 additions & 24 deletions auth/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ import (
"fmt"
"net/http"

"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/aperture/mint"
"github.com/lightningnetwork/lnd/lnrpc"
)

// LsatAuthenticator is an authenticator that uses the LSAT protocol to
// L402Authenticator is an authenticator that uses the L402 protocol to
// authenticate requests.
type LsatAuthenticator struct {
type L402Authenticator struct {
minter Minter
checker InvoiceChecker
}

// A compile time flag to ensure the LsatAuthenticator satisfies the
// A compile time flag to ensure the L402Authenticator satisfies the
// Authenticator interface.
var _ Authenticator = (*LsatAuthenticator)(nil)
var _ Authenticator = (*L402Authenticator)(nil)

// NewLsatAuthenticator creates a new authenticator that authenticates requests
// based on LSAT tokens.
func NewLsatAuthenticator(minter Minter,
checker InvoiceChecker) *LsatAuthenticator {
// NewL402Authenticator creates a new authenticator that authenticates requests
// based on L402 tokens.
func NewL402Authenticator(minter Minter,
checker InvoiceChecker) *L402Authenticator {

return &LsatAuthenticator{
return &L402Authenticator{
minter: minter,
checker: checker,
}
Expand All @@ -37,11 +37,11 @@ func NewLsatAuthenticator(minter Minter,
// to a given backend service.
//
// NOTE: This is part of the Authenticator interface.
func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool {
func (l *L402Authenticator) Accept(header *http.Header, serviceName string) bool {
// Try reading the macaroon and preimage from the HTTP header. This can
// be in different header fields depending on the implementation and/or
// protocol.
mac, preimage, err := lsat.FromHeader(header)
mac, preimage, err := l402.FromHeader(header)
if err != nil {
log.Debugf("Deny: %v", err)
return false
Expand All @@ -52,9 +52,9 @@ func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool
Preimage: preimage,
TargetService: serviceName,
}
err = l.minter.VerifyLSAT(context.Background(), verificationParams)
err = l.minter.VerifyL402(context.Background(), verificationParams)
if err != nil {
log.Debugf("Deny: LSAT validation failed: %v", err)
log.Debugf("Deny: L402 validation failed: %v", err)
return false
}

Expand All @@ -71,35 +71,54 @@ func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool
return true
}

const (
// lsatAuthScheme is an outdated RFC 7235 auth-scheme used by aperture.
lsatAuthScheme = "LSAT"

// l402AuthScheme is the current RFC 7235 auth-scheme used by aperture.
l402AuthScheme = "L402"
)

// FreshChallengeHeader returns a header containing a challenge for the user to
// complete.
//
// NOTE: This is part of the Authenticator interface.
func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request,
func (l *L402Authenticator) FreshChallengeHeader(r *http.Request,
serviceName string, servicePrice int64) (http.Header, error) {

service := lsat.Service{
service := l402.Service{
Name: serviceName,
Tier: lsat.BaseTier,
Tier: l402.BaseTier,
Price: servicePrice,
}
mac, paymentRequest, err := l.minter.MintLSAT(
mac, paymentRequest, err := l.minter.MintL402(
context.Background(), service,
)
if err != nil {
log.Errorf("Error minting LSAT: %v", err)
log.Errorf("Error minting L402: %v", err)
return nil, err
}
macBytes, err := mac.MarshalBinary()
if err != nil {
log.Errorf("Error serializing LSAT: %v", err)
log.Errorf("Error serializing L402: %v", err)
}

str := fmt.Sprintf("LSAT macaroon=\"%s\", invoice=\"%s\"",
header := http.Header{
"Content-Type": []string{"application/grpc"},
}

str := fmt.Sprintf("macaroon=\"%s\", invoice=\"%s\"",
base64.StdEncoding.EncodeToString(macBytes), paymentRequest)
header := r.Header
header.Set("WWW-Authenticate", str)

log.Debugf("Created new challenge header: [%s]", str)
// Old loop software (via ClientInterceptor code of aperture) looks
// for "LSAT" in the first instance of WWW-Authenticate header, so
// legacy header must go first not to break backward compatibility.
lsatValue := lsatAuthScheme + " " + str
l402Value := l402AuthScheme + " " + str
header.Set("WWW-Authenticate", lsatValue)
log.Debugf("Created new challenge header: [%s]", lsatValue)
header.Add("WWW-Authenticate", l402Value)
log.Debugf("Created new challenge header: [%s]", l402Value)

return header, nil
}
54 changes: 38 additions & 16 deletions auth/authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"testing"

"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/l402"
"gopkg.in/macaroon.v2"
)

Expand All @@ -21,8 +21,8 @@ func createDummyMacHex(preimage string) string {
if err != nil {
panic(err)
}
preimageCaveat := lsat.Caveat{Condition: lsat.PreimageKey, Value: preimage}
err = lsat.AddFirstPartyCaveats(dummyMac, preimageCaveat)
preimageCaveat := l402.Caveat{Condition: l402.PreimageKey, Value: preimage}
err = l402.AddFirstPartyCaveats(dummyMac, preimageCaveat)
if err != nil {
panic(err)
}
Expand All @@ -33,9 +33,9 @@ func createDummyMacHex(preimage string) string {
return hex.EncodeToString(macBytes)
}

// TestLsatAuthenticator tests that the authenticator properly handles auth
// TestL402Authenticator tests that the authenticator properly handles auth
// headers and the tokens contained in them.
func TestLsatAuthenticator(t *testing.T) {
func TestL402Authenticator(t *testing.T) {
var (
testPreimage = "49349dfea4abed3cd14f6d356afa83de" +
"9787b609f088c8df09bacc7b4bd21b39"
Expand Down Expand Up @@ -65,21 +65,21 @@ func TestLsatAuthenticator(t *testing.T) {
{
id: "empty auth header",
header: &http.Header{
lsat.HeaderAuthorization: []string{},
l402.HeaderAuthorization: []string{},
},
result: false,
},
{
id: "zero length auth header",
header: &http.Header{
lsat.HeaderAuthorization: []string{""},
l402.HeaderAuthorization: []string{""},
},
result: false,
},
{
id: "invalid auth header",
header: &http.Header{
lsat.HeaderAuthorization: []string{
l402.HeaderAuthorization: []string{
"foo",
},
},
Expand All @@ -88,39 +88,61 @@ func TestLsatAuthenticator(t *testing.T) {
{
id: "invalid macaroon metadata header",
header: &http.Header{
lsat.HeaderMacaroonMD: []string{"foo"},
l402.HeaderMacaroonMD: []string{"foo"},
},
result: false,
},
{
id: "invalid macaroon header",
header: &http.Header{
lsat.HeaderMacaroon: []string{"foo"},
l402.HeaderMacaroon: []string{"foo"},
},
result: false,
},
{
id: "valid auth header",
id: "valid auth header (both LSAT and L402)",
header: &http.Header{
lsat.HeaderAuthorization: []string{
l402.HeaderAuthorization: []string{
"LSAT " + testMacBase64 + ":" +
testPreimage,
"L402 " + testMacBase64 + ":" +
testPreimage,
},
},
result: true,
},
{
id: "valid auth header (LSAT only)",
header: &http.Header{
l402.HeaderAuthorization: []string{
"LSAT " + testMacBase64 + ":" +
testPreimage,
},
},
result: true,
},
{
id: "valid auth header (L402 only)",
header: &http.Header{
l402.HeaderAuthorization: []string{
"L402 " + testMacBase64 + ":" +
testPreimage,
},
},
result: true,
},
{
id: "valid macaroon metadata header",
header: &http.Header{
lsat.HeaderMacaroonMD: []string{
l402.HeaderMacaroonMD: []string{
testMacHex,
}},
result: true,
},
{
id: "valid macaroon header",
header: &http.Header{
lsat.HeaderMacaroon: []string{
l402.HeaderMacaroon: []string{
testMacHex,
},
},
Expand All @@ -129,7 +151,7 @@ func TestLsatAuthenticator(t *testing.T) {
{
id: "valid macaroon header, wrong invoice state",
header: &http.Header{
lsat.HeaderMacaroon: []string{
l402.HeaderMacaroon: []string{
testMacHex,
},
},
Expand All @@ -140,7 +162,7 @@ func TestLsatAuthenticator(t *testing.T) {
)

c := &mockChecker{}
a := auth.NewLsatAuthenticator(&mockMint{}, c)
a := auth.NewL402Authenticator(&mockMint{}, c)
for _, testCase := range headerTests {
c.err = testCase.checkErr
result := a.Accept(testCase.header, "test")
Expand Down
Loading

0 comments on commit 9463266

Please sign in to comment.