auth: Store token in repo
This commit is contained in:
parent
309ecc4052
commit
a88be8d1df
@ -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
|
||||
}
|
||||
|
15
cli/cmd.go
15
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
|
||||
|
@ -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)
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
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/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{
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user