auth: Store token in repo

This commit is contained in:
Łukasz Magiera 2019-07-23 20:49:09 +02:00
parent 309ecc4052
commit a88be8d1df
11 changed files with 107 additions and 23 deletions

View File

@ -1,13 +1,15 @@
package client package client
import ( import (
"net/http"
"github.com/filecoin-project/go-lotus/api" "github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/lib/jsonrpc" "github.com/filecoin-project/go-lotus/lib/jsonrpc"
) )
// NewRPC creates a new http jsonrpc client. // NewRPC creates a new http jsonrpc client.
func NewRPC(addr string) (api.API, error) { func NewRPC(addr string, requestHeader http.Header) (api.API, error) {
var res api.Struct var res api.Struct
_, err := jsonrpc.NewClient(addr, "Filecoin", &res.Internal) _, err := jsonrpc.NewClient(addr, "Filecoin", &res.Internal, requestHeader)
return &res, err return &res, err
} }

View File

@ -2,10 +2,12 @@ package cli
import ( import (
"context" "context"
"net/http"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
logging "github.com/ipfs/go-log"
manet "github.com/multiformats/go-multiaddr-net" manet "github.com/multiformats/go-multiaddr-net"
"gopkg.in/urfave/cli.v2" "gopkg.in/urfave/cli.v2"
@ -14,6 +16,8 @@ import (
"github.com/filecoin-project/go-lotus/node/repo" "github.com/filecoin-project/go-lotus/node/repo"
) )
var log = logging.Logger("cli")
const ( const (
metadataContext = "context" metadataContext = "context"
) )
@ -35,7 +39,16 @@ func getAPI(ctx *cli.Context) (api.API, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return client.NewRPC("ws://" + addr + "/rpc/v0") var headers http.Header
token, err := r.APIToken()
if err != nil {
log.Warnf("Couldn't load CLI token, capabilities may be limited: %w", err)
} else {
headers = map[string][]string{}
headers.Add("Authorization", "Bearer "+string(token))
}
return client.NewRPC("ws://"+addr+"/rpc/v0", headers)
} }
// reqContext returns context for cli execution. Calling it for the first time // reqContext returns context for cli execution. Calling it for the first time

View File

@ -49,6 +49,11 @@ var Cmd = &cli.Command{
return err return err
} }
// Write cli token to the repo if not there yet
if _, err := api.AuthNew(ctx, nil); err != nil {
return err
}
// TODO: properly parse api endpoint (or make it a URL) // TODO: properly parse api endpoint (or make it a URL)
return serveRPC(api, "127.0.0.1:"+cctx.String("api"), api.AuthVerify) return serveRPC(api, "127.0.0.1:"+cctx.String("api"), api.AuthVerify)
}, },

View File

@ -13,7 +13,7 @@ var log = logging.Logger("auth")
type Handler struct { type Handler struct {
Verify func(ctx context.Context, token string) ([]string, error) Verify func(ctx context.Context, token string) ([]string, error)
Next http.HandlerFunc Next http.HandlerFunc
} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"sync/atomic" "sync/atomic"
@ -55,9 +56,9 @@ type ClientCloser func()
// NewClient creates new josnrpc 2.0 client // NewClient creates new josnrpc 2.0 client
// //
// handler must be pointer to a struct with function fields // handler must be pointer to a struct with function fields
// Returned value closes the client connection // Returned value closes the client connectionnil
// TODO: Example // TODO: Example
func NewClient(addr string, namespace string, handler interface{}) (ClientCloser, error) { func NewClient(addr string, namespace string, handler interface{}, requestHeader http.Header) (ClientCloser, error) {
htyp := reflect.TypeOf(handler) htyp := reflect.TypeOf(handler)
if htyp.Kind() != reflect.Ptr { if htyp.Kind() != reflect.Ptr {
return nil, xerrors.New("expected handler to be a pointer") return nil, xerrors.New("expected handler to be a pointer")
@ -71,7 +72,7 @@ func NewClient(addr string, namespace string, handler interface{}) (ClientCloser
var idCtr int64 var idCtr int64
conn, _, err := websocket.DefaultDialer.Dial(addr, nil) conn, _, err := websocket.DefaultDialer.Dial(addr, requestHeader)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -73,7 +73,7 @@ func TestRPC(t *testing.T) {
AddGet func(int) int AddGet func(int) int
StringMatch func(t TestType, i2 int64) (out TestOut, err error) StringMatch func(t TestType, i2 int64) (out TestOut, err error)
} }
closer, err := NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &client) closer, err := NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &client, nil)
require.NoError(t, err) require.NoError(t, err)
defer closer() defer closer()
@ -111,7 +111,7 @@ func TestRPC(t *testing.T) {
var noret struct { var noret struct {
Add func(int) Add func(int)
} }
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &noret) closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &noret, nil)
require.NoError(t, err) require.NoError(t, err)
// this one should actually work // this one should actually work
@ -122,7 +122,7 @@ func TestRPC(t *testing.T) {
var noparam struct { var noparam struct {
Add func() Add func()
} }
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &noparam) closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &noparam, nil)
require.NoError(t, err) require.NoError(t, err)
// shouldn't panic // shouldn't panic
@ -132,7 +132,7 @@ func TestRPC(t *testing.T) {
var erronly struct { var erronly struct {
AddGet func() (int, error) AddGet func() (int, error)
} }
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &erronly) closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &erronly, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = erronly.AddGet() _, err = erronly.AddGet()
@ -144,7 +144,7 @@ func TestRPC(t *testing.T) {
var wrongtype struct { var wrongtype struct {
Add func(string) error Add func(string) error
} }
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &wrongtype) closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &wrongtype, nil)
require.NoError(t, err) require.NoError(t, err)
err = wrongtype.Add("not an int") err = wrongtype.Add("not an int")
@ -156,7 +156,7 @@ func TestRPC(t *testing.T) {
var notfound struct { var notfound struct {
NotThere func(string) error NotThere func(string) error
} }
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &notfound) closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", &notfound, nil)
require.NoError(t, err) require.NoError(t, err)
err = notfound.NotThere("hello?") err = notfound.NotThere("hello?")
@ -203,7 +203,7 @@ func TestCtx(t *testing.T) {
var client struct { var client struct {
Test func(ctx context.Context) Test func(ctx context.Context)
} }
closer, err := NewClient("ws://"+testServ.Listener.Addr().String(), "CtxHandler", &client) closer, err := NewClient("ws://"+testServ.Listener.Addr().String(), "CtxHandler", &client, nil)
require.NoError(t, err) require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
@ -224,7 +224,7 @@ func TestCtx(t *testing.T) {
var noCtxClient struct { var noCtxClient struct {
Test func() Test func()
} }
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "CtxHandler", &noCtxClient) closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "CtxHandler", &noCtxClient, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/chain/types"
"github.com/filecoin-project/go-lotus/miner" "github.com/filecoin-project/go-lotus/miner"
"github.com/filecoin-project/go-lotus/node/client" "github.com/filecoin-project/go-lotus/node/client"
"github.com/filecoin-project/go-lotus/node/repo"
"github.com/gbrlsnchs/jwt/v3" "github.com/gbrlsnchs/jwt/v3"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
@ -26,12 +27,13 @@ var log = logging.Logger("node")
type API struct { type API struct {
client.LocalStorage client.LocalStorage
Host host.Host Host host.Host
Chain *chain.ChainStore Chain *chain.ChainStore
PubSub *pubsub.PubSub PubSub *pubsub.PubSub
Mpool *chain.MessagePool Mpool *chain.MessagePool
Wallet *chain.Wallet Wallet *chain.Wallet
Keystore types.KeyStore Keystore types.KeyStore
Repo repo.LockedRepo
} }
const JWTSecretName = "auth-jwt-private" const JWTSecretName = "auth-jwt-private"
@ -68,7 +70,19 @@ func (a *API) AuthNew(ctx context.Context, perms []string) ([]byte, error) {
return nil, xerrors.Errorf("writing API secret: %w", err) return nil, xerrors.Errorf("writing API secret: %w", err)
} }
// TODO: put cli token in repo // TODO: make this configurable
p := jwtPayload{
Allow: api.AllPermissions,
}
cliToken, err := jwt.Sign(&p, jwt.NewHS256(key.PrivateKey))
if err != nil {
return nil, err
}
if err := a.Repo.SetAPIToken(cliToken); err != nil {
return nil, err
}
} }
p := jwtPayload{ p := jwtPayload{

View File

@ -57,7 +57,7 @@ func rpcBuilder(t *testing.T, n int) []api.API {
testServ := httptest.NewServer(rpcServer) // todo: close testServ := httptest.NewServer(rpcServer) // todo: close
var err error var err error
out[i], err = client.NewRPC("ws://" + testServ.Listener.Addr().String()) out[i], err = client.NewRPC("ws://"+testServ.Listener.Addr().String(), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -27,6 +27,7 @@ import (
const ( const (
fsAPI = "api" fsAPI = "api"
fsAPIToken = "token"
fsConfig = "config.toml" fsConfig = "config.toml"
fsDatastore = "datastore" fsDatastore = "datastore"
fsLibp2pKey = "libp2p.priv" fsLibp2pKey = "libp2p.priv"
@ -109,6 +110,20 @@ func (fsr *FsRepo) APIEndpoint() (multiaddr.Multiaddr, error) {
return apima, nil return apima, nil
} }
func (fsr *FsRepo) APIToken() ([]byte, error) {
p := filepath.Join(fsr.path, fsAPIToken)
f, err := os.Open(p)
if os.IsNotExist(err) {
return nil, ErrNoAPIEndpoint
} else if err != nil {
return nil, err
}
defer f.Close() //nolint: errcheck // Read only op
return ioutil.ReadAll(f)
}
// Lock acquires exclusive lock on this repo // Lock acquires exclusive lock on this repo
func (fsr *FsRepo) Lock() (LockedRepo, error) { func (fsr *FsRepo) Lock() (LockedRepo, error) {
locked, err := fslock.Locked(fsr.path, fsLock) locked, err := fslock.Locked(fsr.path, fsLock)
@ -245,6 +260,13 @@ func (fsr *fsLockedRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error {
return ioutil.WriteFile(fsr.join(fsAPI), []byte(ma.String()), 0644) return ioutil.WriteFile(fsr.join(fsAPI), []byte(ma.String()), 0644)
} }
func (fsr *fsLockedRepo) SetAPIToken(token []byte) error {
if err := fsr.stillValid(); err != nil {
return err
}
return ioutil.WriteFile(fsr.join(fsAPIToken), token, 0600)
}
func (fsr *fsLockedRepo) KeyStore() (types.KeyStore, error) { func (fsr *fsLockedRepo) KeyStore() (types.KeyStore, error) {
if err := fsr.stillValid(); err != nil { if err := fsr.stillValid(); err != nil {
return nil, err return nil, err

View File

@ -13,6 +13,7 @@ import (
var ( var (
ErrNoAPIEndpoint = errors.New("API not running (no endpoint)") ErrNoAPIEndpoint = errors.New("API not running (no endpoint)")
ErrNoAPIToken = errors.New("API token not set")
ErrRepoAlreadyLocked = errors.New("repo is already locked") ErrRepoAlreadyLocked = errors.New("repo is already locked")
ErrClosedRepo = errors.New("repo is no longer open") ErrClosedRepo = errors.New("repo is no longer open")
@ -24,6 +25,9 @@ type Repo interface {
// APIEndpoint returns multiaddress for communication with Lotus API // APIEndpoint returns multiaddress for communication with Lotus API
APIEndpoint() (multiaddr.Multiaddr, error) APIEndpoint() (multiaddr.Multiaddr, error)
// APIToken returns JWT API Token for use in operations that require auth
APIToken() ([]byte, error)
// Lock locks the repo for exclusive use. // Lock locks the repo for exclusive use.
Lock() (LockedRepo, error) Lock() (LockedRepo, error)
} }
@ -45,6 +49,9 @@ type LockedRepo interface {
// so it can be read by API clients // so it can be read by API clients
SetAPIEndpoint(multiaddr.Multiaddr) error SetAPIEndpoint(multiaddr.Multiaddr) error
// SetAPIToken sets JWT API Token for CLI
SetAPIToken([]byte) error
// KeyStore returns store of private keys for Filecoin transactions // KeyStore returns store of private keys for Filecoin transactions
KeyStore() (types.KeyStore, error) KeyStore() (types.KeyStore, error)

View File

@ -18,7 +18,8 @@ import (
type MemRepo struct { type MemRepo struct {
api struct { api struct {
sync.Mutex sync.Mutex
ma multiaddr.Multiaddr ma multiaddr.Multiaddr
token []byte
} }
repoLock chan struct{} repoLock chan struct{}
@ -102,6 +103,15 @@ func (mem *MemRepo) APIEndpoint() (multiaddr.Multiaddr, error) {
return mem.api.ma, nil return mem.api.ma, nil
} }
func (mem *MemRepo) APIToken() ([]byte, error) {
mem.api.Lock()
defer mem.api.Unlock()
if mem.api.ma == nil {
return nil, ErrNoAPIToken
}
return mem.api.token, nil
}
func (mem *MemRepo) Lock() (LockedRepo, error) { func (mem *MemRepo) Lock() (LockedRepo, error) {
select { select {
case mem.repoLock <- struct{}{}: case mem.repoLock <- struct{}{}:
@ -177,6 +187,16 @@ func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error {
return nil return nil
} }
func (lmem *lockedMemRepo) SetAPIToken(token []byte) error {
if err := lmem.checkToken(); err != nil {
return err
}
lmem.mem.api.Lock()
lmem.mem.api.token = token
lmem.mem.api.Unlock()
return nil
}
func (lmem *lockedMemRepo) KeyStore() (types.KeyStore, error) { func (lmem *lockedMemRepo) KeyStore() (types.KeyStore, error) {
if err := lmem.checkToken(); err != nil { if err := lmem.checkToken(); err != nil {
return nil, err return nil, err