multi: add and use DynamicPrice option

This commit adds a DynamicPrice member to the Services struct and uses
its values to determine if a GRPCPricer or DefaultPricer should be
initialised. The commit also updates the sample-conf.yaml file with the
new config options.
This commit is contained in:
Elle Mouton
2021-05-21 15:29:40 +02:00
parent 853b131d80
commit a955d9174b
3 changed files with 138 additions and 7 deletions

View File

@@ -104,21 +104,50 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
resourceName := target.ResourceName(r.URL.Path)
// Determine auth level required to access service and dispatch request
// accordingly.
authLevel := target.AuthRequired(r)
switch {
case authLevel.IsOn():
if !p.authenticator.Accept(&r.Header, target.Name) {
// Determine if the header contains the authentication
// required for the given resource. The call to Accept is
// called in each case body rather than outside the switch so
// as to avoid calling this possibly expensive call for static
// resources.
acceptAuth := p.authenticator.Accept(&r.Header, resourceName)
if !acceptAuth {
price, err := target.pricer.GetPrice(
r.Context(), r.URL.Path,
)
if err != nil {
prefixLog.Errorf("error getting "+
"resource price: %v", err)
sendDirectResponse(
w, r, http.StatusInternalServerError,
"failure fetching "+
"resource price",
)
return
}
// If the price returned is zero, then break out of the
// switch statement and allow access to the service.
if price == 0 {
break
}
prefixLog.Infof("Authentication failed. Sending 402.")
p.handlePaymentRequired(w, r, target.Name, target.Price)
p.handlePaymentRequired(w, r, resourceName, price)
return
}
case authLevel.IsFreebie():
// We only need to respect the freebie counter if the user
// is not authenticated at all.
if !p.authenticator.Accept(&r.Header, target.Name) {
acceptAuth := p.authenticator.Accept(&r.Header, resourceName)
if !acceptAuth {
ok, err := target.freebieDb.CanPass(r, remoteIP)
if err != nil {
prefixLog.Errorf("Error querying freebie db: "+
@@ -130,7 +159,30 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if !ok {
p.handlePaymentRequired(w, r, target.Name, target.Price)
price, err := target.pricer.GetPrice(
r.Context(), r.URL.Path,
)
if err != nil {
prefixLog.Errorf("error getting "+
"resource price: %v", err)
sendDirectResponse(
w, r, http.StatusInternalServerError,
"failure fetching "+
"resource price",
)
return
}
// If the price returned is zero, then break
// out of the switch statement and allow access
// to the service.
if price == 0 {
break
}
p.handlePaymentRequired(
w, r, resourceName, target.Price,
)
return
}
_, err = target.freebieDb.TallyFreebie(r, remoteIP)
@@ -188,7 +240,16 @@ func (p *Proxy) UpdateServices(services []*Service) error {
// Close cleans up the Proxy by closing any remaining open connections.
func (p *Proxy) Close() error {
return nil
var returnErr error
for _, s := range p.services {
if err := s.pricer.Close(); err != nil {
log.Errorf("error while closing the pricer of "+
"service %s: %v", s.Name, err)
returnErr = err
}
}
return returnErr
}
// director is a method that rewrites an incoming request to be forwarded to a

View File

@@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/freebie"
"github.com/lightninglabs/aperture/pricer"
)
var (
@@ -84,6 +85,10 @@ type Service struct {
// service's endpoint.
Price int64 `long:"price" description:"Static LSAT value in satoshis to be used for this service"`
// DynamicPrice holds the config options needed for initialising
// the pricer if a gPRC server is to be used for price data.
DynamicPrice pricer.Config `long:"dynamicprice" description:"Configuration for connecting to the gRPC server to use for the pricer backend"`
// AuthWhitelistPaths is an optional list of regular expressions that
// 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
@@ -93,6 +98,20 @@ type Service struct {
AuthWhitelistPaths []string `long:"authwhitelistpaths" description:"List of regular expressions for paths that don't require authentication'"`
freebieDb freebie.DB
pricer pricer.Pricer
}
// ResourceName returns the string to be used to identify which resource a
// macaroon has access to. If DynamicPrice Enabled option is set to true then
// the service has further restrictions per resource and so the name will
// include both the service name and the specific resource name. Otherwise
// authorisation is only restricted by service name.
func (s *Service) ResourceName(resourcePath string) string {
if s.DynamicPrice.Enabled {
return fmt.Sprintf("%s%s", s.Name, resourcePath)
}
return s.Name
}
// AuthRequired determines the auth level required for a given request.
@@ -170,6 +189,22 @@ func prepareServices(services []*Service) error {
}
}
// If dynamic prices are enabled then use the provided
// DynamicPrice options to initialise a gRPC backed
// pricer client.
if service.DynamicPrice.Enabled {
priceClient, err := pricer.NewGRPCPricer(
&service.DynamicPrice,
)
if err != nil {
return fmt.Errorf("error initializing "+
"pricer: %v", err)
}
service.pricer = priceClient
continue
}
// 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
@@ -186,6 +221,10 @@ func prepareServices(services []*Service) error {
return fmt.Errorf("maximum price exceeded for "+
"service %s", service.Name)
}
// Initialise a default pricer where all resources in a server
// are given the same price.
service.pricer = pricer.NewDefaultPricer(service.Price)
}
return nil
}

View File

@@ -82,8 +82,26 @@ services:
constraints:
"valid_until": "2020-01-01"
# The LSAT value in satoshis for the service.
price: 1
# The LSAT value in satoshis for the service. It is ignored if
# dynamicprice.enabled is set to true.
price: 0
# Options to use for connection to the price serving gRPC server.
dynamicprice:
# Whether or not a gRPC server is available to query price data from. If
# this option is set to true then the 'price' option is ignored.
enabled: true
# The address of the gRPC pricer server.
grpcaddress: "127.0.0.1:10010"
# Whether or not TLS encryption should be used for communications with the
# gRPC server.
insecure: false
# The path to the pricer server's tls.cert. If the 'insecure' option is
# set to true then this path must be set.
tlscertpath: "path-to-pricer-server-tls-cert/tls.cert"
- name: "service2"
hostregexp: "service2.com:8083"
@@ -94,6 +112,19 @@ services:
"valid_until": "2020-01-01"
price: 1
- name: "service3"
hostregexp: "service3.com:8083"
pathregexp: '^/.*$'
address: "123.456.789:8082"
protocol: https
constraints:
"valid_until": "2020-01-01"
dynamicprice:
enbled: true
grpcaddress: 123.456.789:8083
insecure: false
tlscertpath: "path-to-pricer-server-tls-cert/tls.cert"
# Settings for a Tor instance to allow requests over Tor as onion services.
# Configuring Tor is optional.
tor: