auth: Store token in repo
This commit is contained in:
parent
309ecc4052
commit
a88be8d1df
@ -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
|
||||||
}
|
}
|
||||||
|
15
cli/cmd.go
15
cli/cmd.go
@ -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
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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", ¬found)
|
closer, err = NewClient("ws://"+testServ.Listener.Addr().String(), "SimpleServerHandler", ¬found, 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)
|
||||||
}
|
}
|
||||||
|
26
node/api.go
26
node/api.go
@ -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{
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user