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)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
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/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
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/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
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"
|
"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
|
||||||
|
Loading…
Reference in New Issue
Block a user