From a88be8d1df0081a7033f2540f427817aef13c88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 23 Jul 2019 20:49:09 +0200 Subject: [PATCH] auth: Store token in repo --- api/client/client.go | 6 ++++-- cli/cmd.go | 15 ++++++++++++++- daemon/cmd.go | 5 +++++ lib/auth/handler.go | 2 +- lib/jsonrpc/client.go | 7 ++++--- lib/jsonrpc/rpc_test.go | 16 ++++++++-------- node/api.go | 26 ++++++++++++++++++++------ node/node_test.go | 2 +- node/repo/fsrepo.go | 22 ++++++++++++++++++++++ node/repo/interface.go | 7 +++++++ node/repo/memrepo.go | 22 +++++++++++++++++++++- 11 files changed, 107 insertions(+), 23 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index 8d23aea3b..6a412956d 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -1,13 +1,15 @@ package client import ( + "net/http" + "github.com/filecoin-project/go-lotus/api" "github.com/filecoin-project/go-lotus/lib/jsonrpc" ) // 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 - _, err := jsonrpc.NewClient(addr, "Filecoin", &res.Internal) + _, err := jsonrpc.NewClient(addr, "Filecoin", &res.Internal, requestHeader) return &res, err } diff --git a/cli/cmd.go b/cli/cmd.go index ee964efbe..137042105 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -2,10 +2,12 @@ package cli import ( "context" + "net/http" "os" "os/signal" "syscall" + logging "github.com/ipfs/go-log" manet "github.com/multiformats/go-multiaddr-net" "gopkg.in/urfave/cli.v2" @@ -14,6 +16,8 @@ import ( "github.com/filecoin-project/go-lotus/node/repo" ) +var log = logging.Logger("cli") + const ( metadataContext = "context" ) @@ -35,7 +39,16 @@ func getAPI(ctx *cli.Context) (api.API, error) { if err != nil { 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 diff --git a/daemon/cmd.go b/daemon/cmd.go index e89dfa98e..814325aa0 100644 --- a/daemon/cmd.go +++ b/daemon/cmd.go @@ -49,6 +49,11 @@ var Cmd = &cli.Command{ 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) return serveRPC(api, "127.0.0.1:"+cctx.String("api"), api.AuthVerify) }, diff --git a/lib/auth/handler.go b/lib/auth/handler.go index 55cc56846..799914cc4 100644 --- a/lib/auth/handler.go +++ b/lib/auth/handler.go @@ -13,7 +13,7 @@ var log = logging.Logger("auth") type Handler struct { 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) { diff --git a/lib/jsonrpc/client.go b/lib/jsonrpc/client.go index f165d1a9f..074bb0d6d 100644 --- a/lib/jsonrpc/client.go +++ b/lib/jsonrpc/client.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "reflect" "sync/atomic" @@ -55,9 +56,9 @@ type ClientCloser func() // NewClient creates new josnrpc 2.0 client // // handler must be pointer to a struct with function fields -// Returned value closes the client connection +// Returned value closes the client connectionnil // 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) if htyp.Kind() != reflect.Ptr { 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 - conn, _, err := websocket.DefaultDialer.Dial(addr, nil) + conn, _, err := websocket.DefaultDialer.Dial(addr, requestHeader) if err != nil { return nil, err } diff --git a/lib/jsonrpc/rpc_test.go b/lib/jsonrpc/rpc_test.go index 427cf092d..008f9865c 100644 --- a/lib/jsonrpc/rpc_test.go +++ b/lib/jsonrpc/rpc_test.go @@ -73,7 +73,7 @@ func TestRPC(t *testing.T) { AddGet func(int) int 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) defer closer() @@ -111,7 +111,7 @@ func TestRPC(t *testing.T) { var noret struct { 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) // this one should actually work @@ -122,7 +122,7 @@ func TestRPC(t *testing.T) { var noparam struct { 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) // shouldn't panic @@ -132,7 +132,7 @@ func TestRPC(t *testing.T) { var erronly struct { 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) _, err = erronly.AddGet() @@ -144,7 +144,7 @@ func TestRPC(t *testing.T) { var wrongtype struct { 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) err = wrongtype.Add("not an int") @@ -156,7 +156,7 @@ func TestRPC(t *testing.T) { var notfound struct { NotThere func(string) error } - closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", ¬found) + closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", ¬found, nil) require.NoError(t, err) err = notfound.NotThere("hello?") @@ -203,7 +203,7 @@ func TestCtx(t *testing.T) { var client struct { 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) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) @@ -224,7 +224,7 @@ func TestCtx(t *testing.T) { var noCtxClient struct { 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 { t.Fatal(err) } diff --git a/node/api.go b/node/api.go index cd87b7810..6e1135931 100644 --- a/node/api.go +++ b/node/api.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/go-lotus/chain/types" "github.com/filecoin-project/go-lotus/miner" "github.com/filecoin-project/go-lotus/node/client" + "github.com/filecoin-project/go-lotus/node/repo" "github.com/gbrlsnchs/jwt/v3" "github.com/ipfs/go-cid" @@ -26,12 +27,13 @@ var log = logging.Logger("node") type API struct { client.LocalStorage - Host host.Host - Chain *chain.ChainStore - PubSub *pubsub.PubSub - Mpool *chain.MessagePool - Wallet *chain.Wallet + Host host.Host + Chain *chain.ChainStore + PubSub *pubsub.PubSub + Mpool *chain.MessagePool + Wallet *chain.Wallet Keystore types.KeyStore + Repo repo.LockedRepo } 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) } - // 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{ diff --git a/node/node_test.go b/node/node_test.go index 708065d6d..a6c6d4f1f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -57,7 +57,7 @@ func rpcBuilder(t *testing.T, n int) []api.API { testServ := httptest.NewServer(rpcServer) // todo: close 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 { t.Fatal(err) } diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index fa1dd2951..e12a7fa3a 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -27,6 +27,7 @@ import ( const ( fsAPI = "api" + fsAPIToken = "token" fsConfig = "config.toml" fsDatastore = "datastore" fsLibp2pKey = "libp2p.priv" @@ -109,6 +110,20 @@ func (fsr *FsRepo) APIEndpoint() (multiaddr.Multiaddr, error) { 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 func (fsr *FsRepo) Lock() (LockedRepo, error) { 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) } +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) { if err := fsr.stillValid(); err != nil { return nil, err diff --git a/node/repo/interface.go b/node/repo/interface.go index 47e55e10f..bb1bb15ea 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -13,6 +13,7 @@ import ( var ( ErrNoAPIEndpoint = errors.New("API not running (no endpoint)") + ErrNoAPIToken = errors.New("API token not set") ErrRepoAlreadyLocked = errors.New("repo is already locked") ErrClosedRepo = errors.New("repo is no longer open") @@ -24,6 +25,9 @@ type Repo interface { // APIEndpoint returns multiaddress for communication with Lotus API 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() (LockedRepo, error) } @@ -45,6 +49,9 @@ type LockedRepo interface { // so it can be read by API clients SetAPIEndpoint(multiaddr.Multiaddr) error + // SetAPIToken sets JWT API Token for CLI + SetAPIToken([]byte) error + // KeyStore returns store of private keys for Filecoin transactions KeyStore() (types.KeyStore, error) diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 5028074f2..6ea85ca71 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -18,7 +18,8 @@ import ( type MemRepo struct { api struct { sync.Mutex - ma multiaddr.Multiaddr + ma multiaddr.Multiaddr + token []byte } repoLock chan struct{} @@ -102,6 +103,15 @@ func (mem *MemRepo) APIEndpoint() (multiaddr.Multiaddr, error) { 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) { select { case mem.repoLock <- struct{}{}: @@ -177,6 +187,16 @@ func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error { 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) { if err := lmem.checkToken(); err != nil { return nil, err