auth: create invoice request with closure, add challenger

This commit is contained in:
Oliver Gugger
2019-10-29 13:08:16 +01:00
parent c8cbeb9ab1
commit 8cbb4fc4fb
6 changed files with 146 additions and 61 deletions

View File

@@ -1,7 +1,6 @@
package auth package auth
import ( import (
"context"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@@ -9,8 +8,6 @@ import (
"regexp" "regexp"
"github.com/lightninglabs/kirin/macaroons" "github.com/lightninglabs/kirin/macaroons"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"gopkg.in/macaroon-bakery.v2/bakery" "gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon-bakery.v2/bakery/checkers" "gopkg.in/macaroon-bakery.v2/bakery/checkers"
) )
@@ -20,31 +17,27 @@ var (
opWildcard = "*" opWildcard = "*"
) )
type LndAuthenticator struct { // LsatAuthenticator is an authenticator that uses the LSAT protocol to
client lnrpc.LightningClient // authenticate requests.
type LsatAuthenticator struct {
challenger Challenger
macService *macaroons.Service macService *macaroons.Service
} }
// A compile time flag to ensure the LndAuthenticator satisfies the // A compile time flag to ensure the LsatAuthenticator satisfies the
// Authenticator interface. // Authenticator interface.
var _ Authenticator = (*LndAuthenticator)(nil) var _ Authenticator = (*LsatAuthenticator)(nil)
// NewLndAuthenticator creates a new authenticator that is connected to an lnd // NewLsatAuthenticator creates a new authenticator that authenticates requests
// backend and can create new invoices if required. // based on LSAT tokens.
func NewLndAuthenticator(cfg *Config) (*LndAuthenticator, error) { func NewLsatAuthenticator(challenger Challenger) (*LsatAuthenticator, error) {
client, err := lndclient.NewBasicClient(
cfg.LndHost, cfg.TlsPath, cfg.MacDir, cfg.Network,
)
if err != nil {
return nil, err
}
macService, err := macaroons.NewService() macService, err := macaroons.NewService()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &LndAuthenticator{ return &LsatAuthenticator{
client: client, challenger: challenger,
macService: macService, macService: macService,
}, nil }, nil
} }
@@ -53,7 +46,7 @@ func NewLndAuthenticator(cfg *Config) (*LndAuthenticator, error) {
// to a given backend service. // to a given backend service.
// //
// NOTE: This is part of the Authenticator interface. // NOTE: This is part of the Authenticator interface.
func (l *LndAuthenticator) Accept(header *http.Header) bool { func (l *LsatAuthenticator) Accept(header *http.Header) bool {
authHeader := header.Get("Authorization") authHeader := header.Get("Authorization")
log.Debugf("Trying to authorize with header value [%s].", authHeader) log.Debugf("Trying to authorize with header value [%s].", authHeader)
if authHeader == "" { if authHeader == "" {
@@ -103,29 +96,23 @@ func (l *LndAuthenticator) Accept(header *http.Header) bool {
// complete. // complete.
// //
// NOTE: This is part of the Authenticator interface. // NOTE: This is part of the Authenticator interface.
func (l *LndAuthenticator) FreshChallengeHeader(r *http.Request) ( func (l *LsatAuthenticator) FreshChallengeHeader(r *http.Request) (
http.Header, error) { http.Header, error) {
// Obtain a new invoice from lnd first. We need to know the payment hash paymentRequest, paymentHash, err := l.challenger.NewChallenge()
// so we can add it as a caveat to the macaroon.
ctx := context.Background()
invoice := &lnrpc.Invoice{
Memo: "LSAT",
Value: 1,
}
response, err := l.client.AddInvoice(ctx, invoice)
if err != nil { if err != nil {
log.Errorf("Error adding invoice: %v", err) log.Errorf("Error creating new challenge: %v", err)
return nil, err return nil, err
} }
paymentHashHex := hex.EncodeToString(response.RHash)
// Create a new macaroon and add the payment hash as a caveat. // Create a new macaroon and add the payment hash as a caveat.
// The bakery requires at least one operation so we add an "allow all" // The bakery requires at least one operation so we add an "allow all"
// permission set for now. // permission set for now.
mac, err := l.macService.NewMacaroon( mac, err := l.macService.NewMacaroon(
[]bakery.Op{{Entity: opWildcard, Action: opWildcard}}, []string{ []bakery.Op{{Entity: opWildcard, Action: opWildcard}}, []string{
checkers.Condition(macaroons.CondRHash, paymentHashHex), checkers.Condition(
macaroons.CondRHash, paymentHash.String(),
),
}, },
) )
if err != nil { if err != nil {
@@ -135,8 +122,7 @@ func (l *LndAuthenticator) FreshChallengeHeader(r *http.Request) (
str := "LSAT macaroon='%s' invoice='%s'" str := "LSAT macaroon='%s' invoice='%s'"
str = fmt.Sprintf( str = fmt.Sprintf(
str, base64.StdEncoding.EncodeToString(mac), str, base64.StdEncoding.EncodeToString(mac), paymentRequest,
response.GetPaymentRequest(),
) )
header := r.Header header := r.Header
header.Set("WWW-Authenticate", str) header.Set("WWW-Authenticate", str)

View File

@@ -8,17 +8,6 @@ import (
"github.com/lightninglabs/kirin/freebie" "github.com/lightninglabs/kirin/freebie"
) )
type Config struct {
// LndHost is the hostname of the LND instance to connect to.
LndHost string `long:"lndhost" description:"Hostname of the LND instance to connect to"`
TlsPath string `long:"tlspath"`
MacDir string `long:"macdir"`
Network string `long:"network"`
}
type Level string type Level string
func (l Level) lower() string { func (l Level) lower() string {

View File

@@ -1,15 +1,26 @@
package auth package auth
import "net/http" import (
"net/http"
"github.com/lightningnetwork/lnd/lntypes"
)
// Authenticator is the generic interface for validating client headers and // Authenticator is the generic interface for validating client headers and
// returning new challenge headers. // returning new challenge headers.
type Authenticator interface { type Authenticator interface {
// Accept returns whether or not the header successfully authenticates the user // Accept returns whether or not the header successfully authenticates
// to a given backend service. // the user to a given backend service.
Accept(*http.Header) bool Accept(*http.Header) bool
// FreshChallengeHeader returns a header containing a challenge for the user to // FreshChallengeHeader returns a header containing a challenge for the
// complete. // user to complete.
FreshChallengeHeader(r *http.Request) (http.Header, error) FreshChallengeHeader(r *http.Request) (http.Header, error)
} }
// Challenger is an interface for generating new payment challenges.
type Challenger interface {
// NewChallenge creates a new LSAT payment challenge, returning a
// payment request (invoice) and the corresponding payment hash.
NewChallenge() (string, lntypes.Hash, error)
}

74
challenger.go Normal file
View File

@@ -0,0 +1,74 @@
package kirin
import (
"context"
"fmt"
"github.com/lightninglabs/kirin/auth"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
)
// InvoiceRequestGenerator is a function type that returns a new request for the
// lnrpc.AddInvoice call.
type InvoiceRequestGenerator func() (*lnrpc.Invoice, error)
// LndChallenger is a challenger that uses an lnd backend to create new LSAT
// payment challenges.
type LndChallenger struct {
client lnrpc.LightningClient
genInvoiceReq InvoiceRequestGenerator
}
// A compile time flag to ensure the LndChallenger satisfies the
// Challenger interface.
var _ auth.Challenger = (*LndChallenger)(nil)
// NewLndChallenger creates a new challenger that uses the given connection
// details to connect to an lnd backend to create payment challenges.
func NewLndChallenger(cfg *authConfig, genInvoiceReq InvoiceRequestGenerator) (
auth.Challenger, error) {
if genInvoiceReq == nil {
return nil, fmt.Errorf("genInvoiceReq cannot be nil")
}
client, err := lndclient.NewBasicClient(
cfg.LndHost, cfg.TlsPath, cfg.MacDir, cfg.Network,
)
if err != nil {
return nil, err
}
return &LndChallenger{
client: client,
genInvoiceReq: genInvoiceReq,
}, nil
}
// NewChallenge creates a new LSAT payment challenge, returning a payment
// request (invoice) and the corresponding payment hash.
//
// NOTE: This is part of the Challenger interface.
func (l *LndChallenger) NewChallenge() (string, lntypes.Hash, error) {
// 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.
invoice, err := l.genInvoiceReq()
if err != nil {
log.Errorf("Error generating invoice request: %v", err)
return "", lntypes.ZeroHash, err
}
ctx := context.Background()
response, err := l.client.AddInvoice(ctx, invoice)
if err != nil {
log.Errorf("Error adding invoice: %v", err)
return "", lntypes.ZeroHash, err
}
paymentHash, err := lntypes.MakeHash(response.RHash)
if err != nil {
log.Errorf("Error parsing payment hash: %v", err)
return "", lntypes.ZeroHash, err
}
return response.PaymentRequest, paymentHash, nil
}

View File

@@ -2,7 +2,6 @@ package kirin
import ( import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightninglabs/kirin/auth"
"github.com/lightninglabs/kirin/proxy" "github.com/lightninglabs/kirin/proxy"
) )
@@ -17,6 +16,17 @@ var (
defaultMaxLogFileSize = 10 defaultMaxLogFileSize = 10
) )
type authConfig struct {
// LndHost is the hostname of the LND instance to connect to.
LndHost string `long:"lndhost" description:"Hostname of the LND instance to connect to"`
TlsPath string `long:"tlspath"`
MacDir string `long:"macdir"`
Network string `long:"network"`
}
type config struct { type config struct {
// ListenAddr is the listening address that we should use to allow Kirin // ListenAddr is the listening address that we should use to allow Kirin
// to listen for requests. // to listen for requests.
@@ -26,7 +36,7 @@ type config struct {
// is located. // is located.
StaticRoot string `long:"staticroot" description:"The folder where the static content is located."` StaticRoot string `long:"staticroot" description:"The folder where the static content is located."`
Authenticator *auth.Config `long:"authenticator" description:"Configuration for the authenticator."` Authenticator *authConfig `long:"authenticator" description:"Configuration for the authenticator."`
// Services is a list of JSON objects in string format, which specify // Services is a list of JSON objects in string format, which specify
// each backend service to Kirin. // each backend service to Kirin.

View File

@@ -10,6 +10,7 @@ import (
"github.com/lightninglabs/kirin/auth" "github.com/lightninglabs/kirin/auth"
"github.com/lightninglabs/kirin/proxy" "github.com/lightninglabs/kirin/proxy"
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lnrpc"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@@ -37,17 +38,14 @@ func start() error {
return fmt.Errorf("unable to set up logging: %v", err) return fmt.Errorf("unable to set up logging: %v", err)
} }
// Create the auxiliary services the proxy needs to work. // Create the proxy and connect it to lnd.
authenticator, err := auth.NewLndAuthenticator(cfg.Authenticator) genInvoiceReq := func() (*lnrpc.Invoice, error) {
if err != nil { return &lnrpc.Invoice{
return err Memo: "LSAT",
} Value: 1,
servicesProxy, err := proxy.New( }, nil
authenticator, cfg.Services, cfg.StaticRoot,
)
if err != nil {
return err
} }
servicesProxy, err := createProxy(cfg, genInvoiceReq)
server := &http.Server{ server := &http.Server{
Addr: cfg.ListenAddr, Addr: cfg.ListenAddr,
Handler: http.HandlerFunc(servicesProxy.ServeHTTP), Handler: http.HandlerFunc(servicesProxy.ServeHTTP),
@@ -103,6 +101,23 @@ func setupLogging(cfg *config) error {
return build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter) return build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter)
} }
// createProxy creates the proxy with all the services it needs.
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator) (
*proxy.Proxy, error) {
challenger, err := NewLndChallenger(
cfg.Authenticator, genInvoiceReq,
)
if err != nil {
return nil, err
}
authenticator, err := auth.NewLsatAuthenticator(challenger)
if err != nil {
return nil, err
}
return proxy.New(authenticator, cfg.Services, cfg.StaticRoot)
}
// cleanup closes the given server and shuts down the log rotator. // cleanup closes the given server and shuts down the log rotator.
func cleanup(server *http.Server) { func cleanup(server *http.Server) {
err := server.Close() err := server.Close()