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)
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)
ChainGetRandomness func(context.Context, *chain.TipSet) ([]byte, 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)
MpoolPush func(context.Context, *chain.SignedMessage) error
MinerStart func(context.Context, address.Address) error
MinerCreateBlock func(context.Context, address.Address, *chain.TipSet, []chain.Ticket, chain.ElectionProof, []*chain.SignedMessage) (*chain.BlockMsg, 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) `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)
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)
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)
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)
}
}

View File

@ -7,9 +7,9 @@ import (
"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.Register("Filecoin", api)
rpcServer.Register("Filecoin", api.Permissioned(a))
http.Handle("/rpc/v0", rpcServer)
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/filecoin-project/go-bls-sigs v0.0.0-20190718224239-4bc4b8a7bbf8
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/ipfs/go-bitswap v0.1.5
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/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/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/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
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"
"io"
"net/http"
"strings"
"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
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" {
s.handleWS(w, r)
return