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
import (
"context"
"encoding/base64"
"encoding/hex"
"fmt"
@@ -9,8 +8,6 @@ import (
"regexp"
"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/checkers"
)
@@ -20,31 +17,27 @@ var (
opWildcard = "*"
)
type LndAuthenticator struct {
client lnrpc.LightningClient
// LsatAuthenticator is an authenticator that uses the LSAT protocol to
// authenticate requests.
type LsatAuthenticator struct {
challenger Challenger
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.
var _ Authenticator = (*LndAuthenticator)(nil)
var _ Authenticator = (*LsatAuthenticator)(nil)
// NewLndAuthenticator creates a new authenticator that is connected to an lnd
// backend and can create new invoices if required.
func NewLndAuthenticator(cfg *Config) (*LndAuthenticator, error) {
client, err := lndclient.NewBasicClient(
cfg.LndHost, cfg.TlsPath, cfg.MacDir, cfg.Network,
)
if err != nil {
return nil, err
}
// NewLsatAuthenticator creates a new authenticator that authenticates requests
// based on LSAT tokens.
func NewLsatAuthenticator(challenger Challenger) (*LsatAuthenticator, error) {
macService, err := macaroons.NewService()
if err != nil {
return nil, err
}
return &LndAuthenticator{
client: client,
return &LsatAuthenticator{
challenger: challenger,
macService: macService,
}, nil
}
@@ -53,7 +46,7 @@ func NewLndAuthenticator(cfg *Config) (*LndAuthenticator, error) {
// to a given backend service.
//
// 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")
log.Debugf("Trying to authorize with header value [%s].", authHeader)
if authHeader == "" {
@@ -103,29 +96,23 @@ func (l *LndAuthenticator) Accept(header *http.Header) bool {
// complete.
//
// 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) {
// 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.
ctx := context.Background()
invoice := &lnrpc.Invoice{
Memo: "LSAT",
Value: 1,
}
response, err := l.client.AddInvoice(ctx, invoice)
paymentRequest, paymentHash, err := l.challenger.NewChallenge()
if err != nil {
log.Errorf("Error adding invoice: %v", err)
log.Errorf("Error creating new challenge: %v", err)
return nil, err
}
paymentHashHex := hex.EncodeToString(response.RHash)
// 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"
// permission set for now.
mac, err := l.macService.NewMacaroon(
[]bakery.Op{{Entity: opWildcard, Action: opWildcard}}, []string{
checkers.Condition(macaroons.CondRHash, paymentHashHex),
checkers.Condition(
macaroons.CondRHash, paymentHash.String(),
),
},
)
if err != nil {
@@ -135,8 +122,7 @@ func (l *LndAuthenticator) FreshChallengeHeader(r *http.Request) (
str := "LSAT macaroon='%s' invoice='%s'"
str = fmt.Sprintf(
str, base64.StdEncoding.EncodeToString(mac),
response.GetPaymentRequest(),
str, base64.StdEncoding.EncodeToString(mac), paymentRequest,
)
header := r.Header
header.Set("WWW-Authenticate", str)

View File

@@ -8,17 +8,6 @@ import (
"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
func (l Level) lower() string {

View File

@@ -1,15 +1,26 @@
package auth
import "net/http"
import (
"net/http"
"github.com/lightningnetwork/lnd/lntypes"
)
// Authenticator is the generic interface for validating client headers and
// returning new challenge headers.
type Authenticator interface {
// Accept returns whether or not the header successfully authenticates the user
// to a given backend service.
// Accept returns whether or not the header successfully authenticates
// the user to a given backend service.
Accept(*http.Header) bool
// FreshChallengeHeader returns a header containing a challenge for the user to
// complete.
// FreshChallengeHeader returns a header containing a challenge for the
// user to complete.
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 (
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/kirin/auth"
"github.com/lightninglabs/kirin/proxy"
)
@@ -17,6 +16,17 @@ var (
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 {
// ListenAddr is the listening address that we should use to allow Kirin
// to listen for requests.
@@ -26,7 +36,7 @@ type config struct {
// 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
// each backend service to Kirin.

View File

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