Implement API-level auth

This commit is contained in:
Łukasz Magiera 2019-07-18 18:51:21 +02:00
parent 3ecc83efd4
commit 265302f4aa
8 changed files with 120 additions and 9 deletions

60
api/permissioned.go Normal file
View File

@ -0,0 +1,60 @@
package api
import (
"context"
"fmt"
"reflect"
)
type permKey int
var permCtxKey permKey
const (
PermRead = "read" // default
PermWrite = "write"
// todo: more perms once needed (network / sign / call/invoke / miner / etc)
)
var defaultPerms = []string{PermRead}
func WithPerm(ctx context.Context, perms []string) context.Context {
return context.WithValue(ctx, permCtxKey, perms)
}
func Permissioned(a API) API {
var out Struct
rint := reflect.ValueOf(&out.Internal).Elem()
ra := reflect.ValueOf(a)
for f := 0; f < rint.NumField(); f++ {
field := rint.Type().Field(f)
requiredPerm := field.Tag.Get("perm")
if requiredPerm == "" {
requiredPerm = PermRead
}
fn := ra.MethodByName(field.Name)
rint.Field(f).Set(reflect.MakeFunc(field.Type, func(args []reflect.Value) (results []reflect.Value) {
ctx := args[0].Interface().(context.Context)
callerPerms, ok := ctx.Value(permCtxKey).([]string)
if !ok {
callerPerms = defaultPerms
}
for _, callerPerm := range callerPerms {
if callerPerm == requiredPerm {
return fn.Call(args)
}
}
// TODO: return as error
panic(fmt.Sprintf("unauthorized call to %s", field.Name))
}))
}
return &out
}

View File

@ -17,7 +17,7 @@ type Struct struct {
ID func(context.Context) (peer.ID, error) ID func(context.Context) (peer.ID, error)
Version func(context.Context) (Version, error) Version func(context.Context) (Version, error)
ChainSubmitBlock func(ctx context.Context, blk *chain.BlockMsg) error ChainSubmitBlock func(ctx context.Context, blk *chain.BlockMsg) error `perm:"write"`
ChainHead func(context.Context) (*chain.TipSet, error) ChainHead func(context.Context) (*chain.TipSet, error)
ChainGetRandomness func(context.Context, *chain.TipSet) ([]byte, error) ChainGetRandomness func(context.Context, *chain.TipSet) ([]byte, error)
ChainWaitMsg func(context.Context, cid.Cid) (*MsgWait, error) ChainWaitMsg func(context.Context, cid.Cid) (*MsgWait, error)
@ -27,21 +27,21 @@ type Struct struct {
MpoolPending func(context.Context, *chain.TipSet) ([]*chain.SignedMessage, error) MpoolPending func(context.Context, *chain.TipSet) ([]*chain.SignedMessage, error)
MpoolPush func(context.Context, *chain.SignedMessage) error MpoolPush func(context.Context, *chain.SignedMessage) error
MinerStart func(context.Context, address.Address) error MinerStart func(context.Context, address.Address) error `perm:"write"`
MinerCreateBlock func(context.Context, address.Address, *chain.TipSet, []chain.Ticket, chain.ElectionProof, []*chain.SignedMessage) (*chain.BlockMsg, error) MinerCreateBlock func(context.Context, address.Address, *chain.TipSet, []chain.Ticket, chain.ElectionProof, []*chain.SignedMessage) (*chain.BlockMsg, error) `perm:"write"`
WalletNew func(context.Context, string) (address.Address, error) WalletNew func(context.Context, string) (address.Address, error) `perm:"write"`
WalletList func(context.Context) ([]address.Address, error) WalletList func(context.Context) ([]address.Address, error)
WalletBalance func(context.Context, address.Address) (types.BigInt, error) WalletBalance func(context.Context, address.Address) (types.BigInt, error)
WalletSign func(context.Context, address.Address, []byte) (*chain.Signature, error) WalletSign func(context.Context, address.Address, []byte) (*chain.Signature, error) `perm:"write"`
WalletDefaultAddress func(context.Context) (address.Address, error) WalletDefaultAddress func(context.Context) (address.Address, error)
MpoolGetNonce func(context.Context, address.Address) (uint64, error) MpoolGetNonce func(context.Context, address.Address) (uint64, error)
ClientImport func(ctx context.Context, path string) (cid.Cid, error) ClientImport func(ctx context.Context, path string) (cid.Cid, error) `perm:"write"`
ClientListImports func(ctx context.Context) ([]Import, error) ClientListImports func(ctx context.Context) ([]Import, error)
NetPeers func(context.Context) ([]peer.AddrInfo, error) NetPeers func(context.Context) ([]peer.AddrInfo, error)
NetConnect func(context.Context, peer.AddrInfo) error NetConnect func(context.Context, peer.AddrInfo) error `perm:"write"`
NetAddrsListen func(context.Context) (peer.AddrInfo, error) NetAddrsListen func(context.Context) (peer.AddrInfo, error)
} }
} }

View File

@ -7,9 +7,9 @@ import (
"github.com/filecoin-project/go-lotus/lib/jsonrpc" "github.com/filecoin-project/go-lotus/lib/jsonrpc"
) )
func serveRPC(api api.API, addr string) error { func serveRPC(a api.API, addr string) error {
rpcServer := jsonrpc.NewServer() rpcServer := jsonrpc.NewServer()
rpcServer.Register("Filecoin", api) rpcServer.Register("Filecoin", api.Permissioned(a))
http.Handle("/rpc/v0", rpcServer) http.Handle("/rpc/v0", rpcServer)
return http.ListenAndServe(addr, http.DefaultServeMux) return http.ListenAndServe(addr, http.DefaultServeMux)
} }

23
docs/API.md Normal file
View File

@ -0,0 +1,23 @@
TODO: make this into a nicer doc
### Endpoints
By default `127.0.0.1:1234` - daemon stores the api endpoint multiaddr in `~/.lotus/api`
* `http://[api:port]/rpc/v0` - jsonrpc http endpoint
* `ws://[api:port]/rpc/v0` - jsonrpc websocket endpoint
### Auth:
JWT in the `Authorization: Bearer <token>` http header
Permissions:
* `read` - read node state, no private data
* `write` - basically root access, for now
Payload:
```json
{
"Allow": ["read", "write", ...]
}
```

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/filecoin-project/go-bls-sigs v0.0.0-20190718224239-4bc4b8a7bbf8 github.com/filecoin-project/go-bls-sigs v0.0.0-20190718224239-4bc4b8a7bbf8
github.com/filecoin-project/go-leb128 v0.0.0-20190212224330-8d79a5489543 github.com/filecoin-project/go-leb128 v0.0.0-20190212224330-8d79a5489543
github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1
github.com/gorilla/websocket v1.4.0 github.com/gorilla/websocket v1.4.0
github.com/ipfs/go-bitswap v0.1.5 github.com/ipfs/go-bitswap v0.1.5
github.com/ipfs/go-block-format v0.0.2 github.com/ipfs/go-block-format v0.0.2

2
go.sum
View File

@ -52,6 +52,8 @@ github.com/filecoin-project/go-leb128 v0.0.0-20190212224330-8d79a5489543 h1:aMJG
github.com/filecoin-project/go-leb128 v0.0.0-20190212224330-8d79a5489543/go.mod h1:mjrHv1cDGJWDlGmC0eDc1E5VJr8DmL9XMUcaFwiuKg8= github.com/filecoin-project/go-leb128 v0.0.0-20190212224330-8d79a5489543/go.mod h1:mjrHv1cDGJWDlGmC0eDc1E5VJr8DmL9XMUcaFwiuKg8=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 h1:EzDjxMg43q1tA2c0MV3tNbaontnHLplHyFF6M5KiVP0=
github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1/go.mod h1:0eHX/BVySxPc6SE2mZRoppGq7qcEagxdmQnA3dzork8=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=

13
lib/jsonrpc/auth.go Normal file
View File

@ -0,0 +1,13 @@
package jsonrpc
import (
"github.com/gbrlsnchs/jwt/v3"
)
var secret = jwt.NewHS256([]byte("todo: get me from the repo"))
type jwtPayload struct {
Allow []string
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"io" "io"
"net/http" "net/http"
"strings"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -49,6 +50,17 @@ func (s *RPCServer) handleWS(w http.ResponseWriter, r *http.Request) {
// TODO: return errors to clients per spec // TODO: return errors to clients per spec
func (s *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *RPCServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "" {
if !strings.HasPrefix(token, "Bearer ") {
w.WriteHeader(401)
return
}
token = token[len("Bearer "):]
}
if r.Header.Get("Connection") == "Upgrade" { if r.Header.Get("Connection") == "Upgrade" {
s.handleWS(w, r) s.handleWS(w, r)
return return