mirror of
https://github.com/lightninglabs/aperture.git
synced 2025-12-17 17:14:19 +01:00
auth: create invoice request with closure, add challenger
This commit is contained in:
@@ -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)
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
74
challenger.go
Normal 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
|
||||||
|
}
|
||||||
14
config.go
14
config.go
@@ -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.
|
||||||
|
|||||||
35
kirin.go
35
kirin.go
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user