Implement API-level auth
This commit is contained in:
parent
3ecc83efd4
commit
265302f4aa
60
api/permissioned.go
Normal file
60
api/permissioned.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
23
docs/API.md
Normal 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
1
go.mod
@ -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
2
go.sum
@ -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
13
lib/jsonrpc/auth.go
Normal 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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user