aperture: Custom price per service

This commit is contained in:
Elle Mouton
2020-05-10 15:49:48 +02:00
parent 57a5605990
commit 162571ac45
12 changed files with 82 additions and 17 deletions

View File

@@ -96,10 +96,10 @@ func start() error {
} }
// Create the proxy and connect it to lnd. // Create the proxy and connect it to lnd.
genInvoiceReq := func() (*lnrpc.Invoice, error) { genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
return &lnrpc.Invoice{ return &lnrpc.Invoice{
Memo: "LSAT", Memo: "LSAT",
Value: 1, Value: price,
}, nil }, nil
} }
servicesProxy, err := createProxy(cfg, genInvoiceReq, etcdClient) servicesProxy, err := createProxy(cfg, genInvoiceReq, etcdClient)

View File

@@ -59,9 +59,13 @@ func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool
// //
// NOTE: This is part of the Authenticator interface. // NOTE: This is part of the Authenticator interface.
func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request, func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request,
serviceName string) (http.Header, error) { serviceName string, servicePrice int64) (http.Header, error) {
service := lsat.Service{Name: serviceName, Tier: lsat.BaseTier} service := lsat.Service{
Name: serviceName,
Tier: lsat.BaseTier,
Price: servicePrice,
}
mac, paymentRequest, err := l.minter.MintLSAT( mac, paymentRequest, err := l.minter.MintLSAT(
context.Background(), service, context.Background(), service,
) )

View File

@@ -18,7 +18,7 @@ type Authenticator interface {
// FreshChallengeHeader returns a header containing a challenge for the // FreshChallengeHeader returns a header containing a challenge for the
// user to complete. // user to complete.
FreshChallengeHeader(*http.Request, string) (http.Header, error) FreshChallengeHeader(*http.Request, string, int64) (http.Header, error)
} }
// Minter is an entity that is able to mint and verify LSATs for a set of // Minter is an entity that is able to mint and verify LSATs for a set of

View File

@@ -32,7 +32,7 @@ func (a MockAuthenticator) Accept(header *http.Header, _ string) bool {
// FreshChallengeHeader returns a header containing a challenge for the user to // FreshChallengeHeader returns a header containing a challenge for the user to
// complete. // complete.
func (a MockAuthenticator) FreshChallengeHeader(r *http.Request, func (a MockAuthenticator) FreshChallengeHeader(r *http.Request,
_ string) (http.Header, error) { _ string, _ int64) (http.Header, error) {
header := r.Header header := r.Header
header.Set( header.Set(

View File

@@ -12,7 +12,7 @@ import (
// InvoiceRequestGenerator is a function type that returns a new request for the // InvoiceRequestGenerator is a function type that returns a new request for the
// lnrpc.AddInvoice call. // lnrpc.AddInvoice call.
type InvoiceRequestGenerator func() (*lnrpc.Invoice, error) type InvoiceRequestGenerator func(price int64) (*lnrpc.Invoice, error)
// LndChallenger is a challenger that uses an lnd backend to create new LSAT // LndChallenger is a challenger that uses an lnd backend to create new LSAT
// payment challenges. // payment challenges.
@@ -57,10 +57,10 @@ func NewLndChallenger(cfg *authConfig, genInvoiceReq InvoiceRequestGenerator) (
// request (invoice) and the corresponding payment hash. // request (invoice) and the corresponding payment hash.
// //
// NOTE: This is part of the Challenger interface. // NOTE: This is part of the Challenger interface.
func (l *LndChallenger) NewChallenge() (string, lntypes.Hash, error) { func (l *LndChallenger) NewChallenge(price int64) (string, lntypes.Hash, error) {
// Obtain a new invoice from lnd first. We need to know the payment hash // Obtain a new invoice from lnd first. We need to know the payment hash
// so we can add it as a caveat to the macaroon. // so we can add it as a caveat to the macaroon.
invoice, err := l.genInvoiceReq() invoice, err := l.genInvoiceReq(price)
if err != nil { if err != nil {
log.Errorf("Error generating invoice request: %v", err) log.Errorf("Error generating invoice request: %v", err)
return "", lntypes.ZeroHash, err return "", lntypes.ZeroHash, err

View File

@@ -46,6 +46,9 @@ type Service struct {
// Tier is the tier of the LSAT-enabled service. // Tier is the tier of the LSAT-enabled service.
Tier ServiceTier Tier ServiceTier
// Price of service LSAT in satoshis.
Price int64
} }
// NewServicesCaveat creates a new services caveat with the provided caveats. // NewServicesCaveat creates a new services caveat with the provided caveats.

View File

@@ -27,7 +27,7 @@ type Challenger interface {
// payment request. The payment hash is also returned as a convenience // payment request. The payment hash is also returned as a convenience
// to avoid having to decode the payment request in order to retrieve // to avoid having to decode the payment request in order to retrieve
// its payment hash. // its payment hash.
NewChallenge() (string, lntypes.Hash, error) NewChallenge(price int64) (string, lntypes.Hash, error)
} }
// SecretStore is the store responsible for storing LSAT secrets. These secrets // SecretStore is the store responsible for storing LSAT secrets. These secrets
@@ -93,9 +93,13 @@ func New(cfg *Config) *Mint {
func (m *Mint) MintLSAT(ctx context.Context, func (m *Mint) MintLSAT(ctx context.Context,
services ...lsat.Service) (*macaroon.Macaroon, string, error) { services ...lsat.Service) (*macaroon.Macaroon, string, error) {
// Let the LSAT value as the price of the most expensive of the
// services.
price := maximumPrice(services)
// We'll start by retrieving a new challenge in the form of a Lightning // We'll start by retrieving a new challenge in the form of a Lightning
// payment request to present the requester of the LSAT with. // payment request to present the requester of the LSAT with.
paymentRequest, paymentHash, err := m.cfg.Challenger.NewChallenge() paymentRequest, paymentHash, err := m.cfg.Challenger.NewChallenge(price)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@@ -143,6 +147,20 @@ func (m *Mint) MintLSAT(ctx context.Context,
return mac, paymentRequest, nil return mac, paymentRequest, nil
} }
// maximumPrice determines the necessary price to use for a collection
// of services.
func maximumPrice(services []lsat.Service) int64 {
var max int64
for _, service := range services {
if service.Price > max {
max = service.Price
}
}
return max
}
// createUniqueIdentifier creates a new LSAT identifier bound to a payment hash // createUniqueIdentifier creates a new LSAT identifier bound to a payment hash
// and a randomly generated ID. // and a randomly generated ID.
func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) { func createUniqueIdentifier(paymentHash lntypes.Hash) ([]byte, error) {

View File

@@ -26,7 +26,7 @@ func newMockChallenger() *mockChallenger {
return &mockChallenger{} return &mockChallenger{}
} }
func (d *mockChallenger) NewChallenge() (string, lntypes.Hash, error) { func (d *mockChallenger) NewChallenge(price int64) (string, lntypes.Hash, error) {
return testPayReq, testHash, nil return testPayReq, testHash, nil
} }

View File

@@ -97,7 +97,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case authLevel.IsOn(): case authLevel.IsOn():
if !p.authenticator.Accept(&r.Header, target.Name) { if !p.authenticator.Accept(&r.Header, target.Name) {
prefixLog.Infof("Authentication failed. Sending 402.") prefixLog.Infof("Authentication failed. Sending 402.")
p.handlePaymentRequired(w, r, target.Name) p.handlePaymentRequired(w, r, target.Name, target.Price)
return return
} }
@@ -116,7 +116,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
if !ok { if !ok {
p.handlePaymentRequired(w, r, target.Name) p.handlePaymentRequired(w, r, target.Name, target.Price)
return return
} }
_, err = target.freebieDb.TallyFreebie(r, remoteIP) _, err = target.freebieDb.TallyFreebie(r, remoteIP)
@@ -281,11 +281,11 @@ func addCorsHeaders(header http.Header) {
// handlePaymentRequired returns fresh challenge header fields and status code // handlePaymentRequired returns fresh challenge header fields and status code
// to the client signaling that a payment is required to fulfil the request. // to the client signaling that a payment is required to fulfil the request.
func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request, func (p *Proxy) handlePaymentRequired(w http.ResponseWriter, r *http.Request,
serviceName string) { serviceName string, servicePrice int64) {
addCorsHeaders(r.Header) addCorsHeaders(r.Header)
header, err := p.authenticator.FreshChallengeHeader(r, serviceName) header, err := p.authenticator.FreshChallengeHeader(r, serviceName, servicePrice)
if err != nil { if err != nil {
log.Errorf("Error creating new challenge header: %v", err) log.Errorf("Error creating new challenge header: %v", err)
sendDirectResponse( sendDirectResponse(

View File

@@ -9,6 +9,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/aperture/auth" "github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/freebie" "github.com/lightninglabs/aperture/freebie"
) )
@@ -19,6 +20,16 @@ var (
filePrefixBase64 = filePrefix + "+base64" filePrefixBase64 = filePrefix + "+base64"
) )
const (
// defaultServicePrice is price in satoshis to be used as the default
// service price.
defaultServicePrice = 1
// maxServicePrice is the maximum price in satoshis that can be used
// to create an invoice through lnd.
maxServicePrice = btcutil.SatoshiPerBitcoin * 100000
)
// Service generically specifies configuration data for backend services to the // Service generically specifies configuration data for backend services to the
// Kirin proxy. // Kirin proxy.
type Service struct { type Service struct {
@@ -69,6 +80,10 @@ type Service struct {
// correspond to the caveat's condition. // correspond to the caveat's condition.
Constraints map[string]string `long:"constraints" description:"The service constraints to enforce at the base tier"` Constraints map[string]string `long:"constraints" description:"The service constraints to enforce at the base tier"`
// Price is the custom LSAT value in satoshis to be used for the
// service's endpoint.
Price int64 `long:"price" description:"Static LSAT value in satoshis to be used for this service"`
// AuthWhitelistPaths is an optional list of regular expressions that // AuthWhitelistPaths is an optional list of regular expressions that
// are matched against the path of the URL of a request. If the request // are matched against the path of the URL of a request. If the request
// URL matches any of those regular expressions, the call is treated as // URL matches any of those regular expressions, the call is treated as
@@ -154,6 +169,23 @@ func prepareServices(services []*Service) error {
"whitelist: %v", err) "whitelist: %v", err)
} }
} }
// Check that the price for the service is not negative and not
// more than the maximum amount allowed by lnd. If no price, or
// a price of zero satoshis, is set the then default price of 1
// satoshi is to be used.
switch {
case service.Price == 0:
log.Debugf("Using default LSAT price of %v satoshis for "+
"service %s.", defaultServicePrice, service.Name)
service.Price = defaultServicePrice
case service.Price < 0:
return fmt.Errorf("negative price set for "+
"service %s", service.Name)
case service.Price > maxServicePrice:
return fmt.Errorf("maximum price exceeded for "+
"service %s", service.Name)
}
} }
return nil return nil
} }

View File

@@ -78,6 +78,9 @@ services:
constraints: constraints:
"valid_until": "2020-01-01" "valid_until": "2020-01-01"
# The LSAT value in satoshis for the service.
price: 1
- name: "service2" - name: "service2"
hostregexp: "service2.com:8083" hostregexp: "service2.com:8083"
pathregexp: '^/.*$' pathregexp: '^/.*$'
@@ -85,6 +88,7 @@ services:
protocol: https protocol: https
constraints: constraints:
"valid_until": "2020-01-01" "valid_until": "2020-01-01"
price: 1
# Settings for a Tor instance to allow requests over Tor as onion services. # Settings for a Tor instance to allow requests over Tor as onion services.
# Configuring Tor is optional. # Configuring Tor is optional.

View File

@@ -27,7 +27,11 @@ func newStaticServiceLimiter(proxyServices []*proxy.Service) *staticServiceLimit
constraints := make(map[lsat.Service][]lsat.Caveat) constraints := make(map[lsat.Service][]lsat.Caveat)
for _, proxyService := range proxyServices { for _, proxyService := range proxyServices {
s := lsat.Service{Name: proxyService.Name, Tier: lsat.BaseTier} s := lsat.Service{
Name: proxyService.Name,
Tier: lsat.BaseTier,
Price: proxyService.Price,
}
capabilities[s] = lsat.NewCapabilitiesCaveat( capabilities[s] = lsat.NewCapabilitiesCaveat(
proxyService.Name, proxyService.Capabilities, proxyService.Name, proxyService.Capabilities,
) )