# Simple Summary (概述)
链服务对其用户(SP 等)数据隔离,并开放访问接口。
# Abstract (功能简介)
SP
的很多业务数据保存在链服务端,并由其负责跟踪与维护。
为了 SP
用户能够管理自己的数据,如:订单,消息,出块,转账等,需要链服务对用户的数据进行隔离,并开放请求的接口。
关联:
- [x] 提案的issue (opens new window)
# Motivation (来源/背景)
Venus
系统分为 云组件
和 本地组件
,在多数情况下,云组件
和 本地组件
的维护者不是同一类人,比如孵化器就是专门维护一套链服务给众多的 SP 终端用户使用。云组件
在向不同用户提供服务的同时,为了确保用户数据的安全,也限制了用户对大部分 用户数据
的访问,需要新增一种机制,使得用户能够访问自己的 用户数据
的同时,隔离其他用户的数据,确保每一位用户的数据安全
# Specification (feature Spec)
链服务
: 是指Sophon
所提供的,包括 链上信息的查询,发布消息 等细项在内的服务。订单服务
: 是指Droplet
提供的,包括检索订单
的创建和查询,检索订单
的创建和查询,存储服务提供商定价查询 等细项在内的服务。用户
是指链服务
的使用者,这要包括三类群体:SC
:借助部署在订单服务
发起存储订单的客户;SP
:使用链服务
和Filecoin 网络
进行交互,或者使用订单服务
承接存储订单的存储服务提供商;其他
: 除了上述两个群体的其他用户,比如使用链服务
获取链上数据或者和链进行交互的开发者等。
云组件
: 是指包括venus-auth
,venus-gateway
,venus
,venus-messager
,venus-miner
,venus-market
等系列软件在内的软件实体,其特点是一套云组件
可以为多个不同的用户
提供服务。云组件又可以划分为Sophon
和Droplet
两个部分,其中Sophon
负责提供链服务
,Droplet
负责提供订单服务
.本地组件
: 是指包括venus-cluster
,venus-tool
等软件在内的软件实体。本地组件
依赖于云组件
提供的服务,一套本地组件
一般只为一个用户
提供服务。用户数据
是指用户在使用链服务
以及订单服务
的过程中产生的与用户相关联的数据,包括但不限于:用户的公钥,用户发出的消息,用户发起的订单,用户存储订单中的数据,用户的个性化设置。服务接口
是指链服务
和订单服务
为用户提供的用于使用服务以及访问用户数据的接口,主要是指链服务
和订单服务
所对应的软件实体提供RPC 访问接口.用户数据隔离
: 是指链服务
和订单服务
提供者限制用户只能访问和自己的用户数据
. 通过对服务接口
进行访问控制,限制用户只能访问自己的用户数据
, 是实现用户数据隔离
的方式。用户权限
: 是指链服务
和订单服务
控制用户访问服务接口
的权限级别的划分,包括:读权限
,写权限
,签名权限
等三个用户级别权限,和一个管理员级别权限:管理权限
. 一般来说,用户具有用户级别最高权限,即签名权限
, 但为了方便用户在不同使用情形下进行更加细致的访问控制,用户可以申请更低权限级别的Token
, 比如:读权限
或者写权限
. 只有管理员才能获得管理权限
. 访问不同的服务端口需要不同的权限级别,详细参见 API Reference (opens new window)用户界面
是指用户能够用于访问服务接口
的软件或者其他类型的工具,包括但不限于:Democles
,venus-wallet
,market-client
,venus-tool
等直接访问链服务
或者订单服务
服务的软件; 或者能够直接访问服务接口
的 curl , 或者用户自己开发的工具。
# Design Rationale (设计思路)
通过 Token
区别不同用户身份,以及访问级别。
服务接口
的实现中针对不同的 用户身份
, 进行不同的处理,从而实现 用户数据隔离
.
# Backwards Compatibility(兼容性)
# Test Cases (测试用例)
# Security Considerations (安全考量)
- 应确保只有管理员能够获取到 'admin' 级别的权限,防止用户数据泄漏
# Implementation(实施)
# 用户身份识别
通过 Token
获取用户身份
func JwtUserFromToken(token string) (string, error) {
sks := strings.Split(token, ".")
if len(sks) < 1 {
return "", fmt.Errorf("can't parse user from input token")
}
dec, err := DecodeToBytes([]byte(sks[1]))
if err != nil {
return "", err
}
payload := &JWTPayload{}
err = json.Unmarshal(dec, payload)
return payload.Name, err
}
将用户名称注入到 context
中
ctx = CtxWithName(ctx, name)
func CtxWithName(ctx context.Context, v string) context.Context {
return ctxWithString(ctx, accountKey, v)
}
func ctxWithString(ctx context.Context, k CtxKey, v string) context.Context {
return context.WithValue(ctx, k, v)
}
服务接口
中通过 context
获取用户名称
func GetCtxName(ctx context.Context) string {
return ctx.Value(accountKey).(string)
}
# 目标数据的识别
仅仅只是知道 用户身份
是不够的的,我们还需要知道,该用户访问的 目标数据
是什么,以及该目标数据
是属于哪个 用户
的。例如,一个 用户
可能有多个 矿工
, 但是他只能访问自己的 矿工
的数据,不能访问其他 用户
的 矿工
的数据。
绝大多数情况下,我们都可以通过 用户
所访问的 服务接口
来确定 目标数据
的类型,可以根据 目标数据
类型中的 关键字段
来确定 目标数据
的归属。例如当用户访问 消息相关的服务接口时,可以通过 消息类型
的发送者字段来确定该数据的拥有者是谁,进而判断访问者是否具有该数据的访问权限。
在现在的实现中,我们可以通过三种类型的 关键数据
, 来确定访问者是否具有访问 目标数据
的权限:
# 用户名称
在用户管理相关的 服务接口
中,往往会出现 用户名称
作为 关键数据
的情况,例如 用户
的 矿工
列表,用户
的 Signer
列表等。通过 用户名称
我们可以直接判断访问者是否具有访问该数据的权限。
// checkPermissionByUser check weather the user has admin permission or is match the username passed in
func CheckPermissionByName(ctx context.Context, name string) error {
if auth.HasPerm(ctx, []auth.Permission{}, core.PermAdmin) {
return nil
}
user, exist := CtxGetName(ctx)
if !exist || user != name {
return ErrorPermissionDeny
}
return nil
}
# 钱包地址
在和 消息
有关联的 服务接口
中,往往会涉及到消息的发送者,也就是签名该消息的钱包地址。通过该地址,我们可以判断该消息是否属于访问者的,从而判断访问者是否具有访问该数据的权限。
func CheckPermissionBySigner(ctx context.Context, client IAuthClient, addrs ...address.Address) error {
if auth.HasPerm(ctx, []auth.Permission{}, core.PermAdmin) {
return nil
}
user, exist := CtxGetName(ctx)
if !exist {
return ErrorUserNotFound
}
for _, wAddr := range addrs {
ok, err := client.SignerExistInUser(ctx, user, wAddr)
if err != nil {
return fmt.Errorf("check signer exist in user fail %s failed when check permission: %s", wAddr.String(), err)
}
if !ok {
return ErrorPermissionDeny
}
}
return nil
}
# 矿工地址
func CheckPermissionByMiner(ctx context.Context, client IAuthClient, addrs ...address.Address) error {
if auth.HasPerm(ctx, []auth.Permission{}, core.PermAdmin) {
return nil
}
user, exist := CtxGetName(ctx)
if !exist {
return ErrorUserNotFound
}
for _, mAddr := range addrs {
ok, err := client.MinerExistInUser(ctx, user, mAddr)
if err != nil {
return fmt.Errorf("check miner exist in user fail %s failed when check permission: %s", mAddr.String(), err)
}
if !ok {
return ErrorPermissionDeny
}
}
return nil
}
# 实现的细节
本小节主要叙述在实际实现的过程中涉及到的 服务接口
的变动。
venus
组件不涉及 用户数据
, 不需要做相应变动.
venus-gateway
涉及 用户数据
部分的 接口
仅对管理员开放,不需要变动.
所以,主要涉及到一下四个 云组件
的接口:
# venus-auth
接口
venus-auth
本身并无 用户身份
和 权限级别
的检查,新增如下:
// Read 权限
Verify(ctx context.Context, token string) (*JWTPayload, error) // read
// Admin 权限:
GenerateToken(ctx context.Context, cp *JWTPayload) (string, error)
Tokens(ctx context.Context, skip, limit int64) ([]*TokenInfo, error)
GetToken(c context.Context, token string) (*TokenInfo, error)
CreateUser(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error)
VerifyUsers(ctx context.Context, req *VerifyUsersReq) error
ListUsers(ctx context.Context, req *ListUsersRequest) (ListUsersResponse, error)
HasUser(ctx context.Context, req *HasUserRequest) (bool, error)
UpdateUser(ctx context.Context, req *UpdateUserRequest) error
DeleteUser(ctx *gin.Context, req *DeleteUserRequest) error
RecoverUser(ctx *gin.Context, req *RecoverUserRequest) error
GetUserRateLimits(ctx context.Context, req *GetUserRateLimitsReq) (GetUserRateLimitResponse, error)
UpsertUserRateLimit(ctx context.Context, req *UpsertUserRateLimitReq) (string, error)
DelUserRateLimit(ctx context.Context, req *DelUserRateLimitReq) error
HasMiner(ctx context.Context, req *HasMinerRequest) (bool, error)
GetUserByMiner(ctx context.Context, req *GetUserByMinerRequest) (*OutputUser, error)
RegisterSigners(ctx context.Context, req *RegisterSignersReq) error
UnregisterSigners(ctx context.Context, req *UnregisterSignersReq) error
HasSigner(ctx context.Context, req *HasSignerReq) (bool, error)
GetUserBySigner(ctx context.Context, req *GetUserBySignerReq) ([]*OutputUser, error)
UpsertMiner(ctx context.Context, req *UpsertMinerReq) (bool, error)
// Admin 权限 或者 目标数据拥有者 (意味着这部分接口会对部分非 admin 权限开放)
RemoveToken(ctx context.Context, token string) error // +tokenOwner
RecoverToken(ctx context.Context, token string) error // +tokenOwner
GetTokenByName(c context.Context, name string) ([]*TokenInfo, error) // +tokenOwner
GetUser(ctx context.Context, req *GetUserRequest) (*OutputUser, error) // +userOwner
MinerExistInUser(ctx context.Context, req *MinerExistInUserRequest) (bool, error) // +userOwner
ListMiners(ctx context.Context, req *ListMinerReq) (ListMinerResp, error) // +userOwner
DelMiner(ctx context.Context, req *DelMinerReq) (bool, error) // +minerOwner
SignerExistInUser(ctx context.Context, req *SignerExistInUserReq) (bool, error) // +userOwner
ListSigner(ctx context.Context, req *ListSignerReq) (ListSignerResp, error) // +userOwner
DelSigner(ctx context.Context, req *DelSignerReq) (bool, error) // +signerOwner
# venus-miner
在非 管理权限
的接口增加了用户身份
的检查,接口权限变动如下:
UpdateAddress(context.Context, int64, int64) ([]types.MinerInfo, error) //perm:write -> admin
WarmupForMiner(context.Context, address.Address) error //perm:admin -> write
Start(context.Context, []address.Address) error //perm:admin -> write
Stop(context.Context, []address.Address) error //perm:admin -> write
ListAddress(context.Context) ([]types.MinerInfo, error) //perm:read
StatesForMining(context.Context, []address.Address) ([]types.MinerState, error) //perm:read
CountWinners(context.Context, []address.Address, abi.ChainEpoch, abi.ChainEpoch) ([]types.CountWinners, error) //perm:read
# venus-messager
// 不需要用户隔离的接口 (管理员级别的接口或者公开数据接口)
SetSharedParams(ctx context.Context, params *mtypes.SharedSpec) //perm:admin
SetLogLevel(ctx context.Context, subsystem, level string) //perm:admin
SaveNode(ctx context.Context, node *mtypes.Node) //perm:admin
ListNode(ctx context.Context) //perm:admin
HasNode(ctx context.Context, name string) //perm:admin
GetSharedParams(ctx context.Context) //perm:admin
GetNode(ctx context.Context, name string) //perm:admin
DeleteNode(ctx context.Context, name string) //perm:admin
ListMessageByAddress(ctx context.Context, addr address.Address) //perm:admin
ListMessageByFromState(ctx context.Context, from address.Address, state mtypes.MessageState, isAsc bool, pageIndex, pageSize int) //perm:admin
UpdateAllFilledMessage(ctx context.Context) //perm:admin
RepublishMessage(ctx context.Context, id string) //perm:admin
LogList(context.Context) //perm:write -> admin
NetPeers() //perm:read -> admin
NetFindPeer() //perm:read -> admin
NetAddrListen() //perm:read -> admin
NetConnect() //perm:admin
UpdateNonce(ctx context.Context, addr address.Address, nonce uint64) //perm:admin
HasMessageByUid(ctx context.Context, id string) //perm:read
// 已经实现用户隔离的接口
HasAddress(ctx context.Context, addr address.Address) //perm:read
GetMessageByUnsignedCid(ctx context.Context, cid cid.Cid) //perm:read
GetMessageByUid(ctx context.Context, id string) //perm:read
GetMessageBySignedCid(ctx context.Context, cid cid.Cid) //perm:read
GetMessageByFromAndNonce(ctx context.Context, from address.Address, nonce uint64) //perm:read
PushMessageWithId(ctx context.Context, id string, msg *types.Message, meta *mtypes.SendSpec) //perm:write
PushMessage(ctx context.Context, msg *types.Message, meta *mtypes.SendSpec) //perm:write
WalletHas(ctx context.Context, addr address.Address) //perm:read
WaitMessage(ctx context.Context, id string, confidence uint64) //perm:read
UpdateMessageStateByID(ctx context.Context, id string, state mtypes.MessageState) //perm:admin -> write
UpdateFilledMessageByID(ctx context.Context, id string) //perm:admin -> write
SetFeeParams(ctx context.Context, params *mtypes.AddressSpec) //perm:admin -> write
ReplaceMessage(ctx context.Context, params *mtypes.ReplacMessageParams) //perm:admin -> write
RecoverFailedMsg(ctx context.Context, addr address.Address) //perm:admin -> write
MarkBadMessage(ctx context.Context, id string) //perm:admin -> write
ListMessage(ctx context.Context , p *types.MsgQueryParams) //perm:admin -> read
ListFailedMessage(ctx context.Context) //perm:admin -> read
ListBlockedMessage(ctx context.Context, addr address.Address, d time.Duration) //perm:admin -> read
ListAddress(ctx context.Context) //perm:admin -> read
GetAddress(ctx context.Context, addr address.Address) //perm:admin -> read
ForbiddenAddress(ctx context.Context, addr address.Address) //perm:admin -> write
DeleteAddress(ctx context.Context, addr address.Address) //perm:admin -> write
ClearUnFillMessage(ctx context.Context, addr address.Address) //perm:admin ->write
ActiveAddress(ctx context.Context, addr address.Address) //perm:admin ->write
Send(ctx context.Context, params mtypes.QuickSendParams) //perm:admin -> sign
SetSelectMsgNum(ctx context.Context, addr address.Address, num uint64) //perm:admin -> write
// 参数变动
ListMessage(ctx context.Context ) -> ListMessage(ctx context.Context , p *types.MsgQueryParams)
# venus-market
// 不需要做用户数据隔离的接口 (开放数据)
PiecesListPieces //perm:read
PiecesListCidInfos //perm:read
PiecesGetPieceInfo //perm:read
PiecesGetCIDInfo //perm:read
MessagerWaitMessage //perm:read
MessagerGetMessage //perm:read
NetAddrsListen //perm:read
ID //perm:read
ResponseMarketEvent //perm:read
ListenMarketEvent //perm:read
ListPieceStorageInfos //perm:read
MarketListAsk //perm:read
MarketListRetrievalAsk //perm:read
// 不对普通用户开放的接口 (管理员级别的接口)
MarketListDataTransfers //perm:read -> admin
MarketDataTransferUpdates //perm:read -> admin
MarketRestartDataTransfer //perm:read -> admin
MarketCancelDataTransfer //perm:read -> admin
ImportV1Data //perm:admin
MarketPublishPendingDeals //perm:admin
DagstoreListShards //perm:admin
DagstoreInitializeShard //perm:admin
DagstoreRecoverShard //perm:admin
DagstoreInitializeAll //perm:admin
DagstoreInitializeStorage //perm:admin
DagstoreGC //perm:admin
AddFsPieceStorage //perm:admin
AddS3PieceStorage //perm:admin
RemovePieceStorage //perm:admin
// 暂时不进行用户数据隔离的接口
MarketListRetrievalDeals //perm:read
// 进行用户数据隔离的接口
ActorList //perm:read
ActorExist //perm:read
ActorSectorSize //perm:read
MarketListDeals //perm:read
MarketGetDealUpdates //perm:read
MarketListIncompleteDeals //perm:read
MarketGetAsk //perm:read
MarketGetRetrievalAsk //perm:read
DealsConsiderOnlineStorageDeals //perm:read
DealsConsiderOnlineRetrievalDeals //perm:read
DealsPieceCidBlocklist //perm:read
DealsConsiderOfflineStorageDeals //perm:read
DealsConsiderOfflineRetrievalDeals //perm:read
DealsConsiderVerifiedStorageDeals //perm:read
DealsConsiderUnverifiedStorageDeals //perm:read
SectorGetExpectedSealDuration //perm:read
DealsMaxStartDelay //perm:read
DealsPublishMsgPeriod //perm:read
MarketMaxDealsPerPublishMsg //perm:read
DealsMaxProviderCollateralMultiplier //perm:read
DealsMaxPublishFee //perm:read
MarketMaxBalanceAddFee //perm:read
GetDeals //perm:read
GetUnPackedDeals //perm:read
PaychVoucherList //perm:read
GetStorageDealStatistic //perm:read
GetRetrievalDealStatistic //perm:read
MarketImportDealData //perm:write
MarketImportPublishedDeal //perm:write
MarketPendingDeals //perm:write
DealsSetConsiderOnlineStorageDeals //perm:write
DealsSetConsiderOnlineRetrievalDeals //perm:write
DealsSetPieceCidBlocklist //perm:write
DealsSetConsiderOfflineStorageDeals //perm:write
DealsSetConsiderOfflineRetrievalDeals //perm:write
DealsSetConsiderVerifiedStorageDeals //perm:write
DealsSetConsiderUnverifiedStorageDeals //perm:write
SectorSetExpectedSealDuration //perm:write
DealsSetMaxStartDelay //perm:write
DealsSetPublishMsgPeriod //perm:write
MarketSetMaxDealsPerPublishMsg //perm:write
DealsSetMaxProviderCollateralMultiplier //perm:write
DealsSetMaxPublishFee //perm:write
MarketSetMaxBalanceAddFee //perm:write
MessagerPushMessage //perm:write
MarkDealsAsPacking //perm:write
UpdateDealOnPacking //perm:write
UpdateDealStatus //perm:write
AssignUnPackedDeals //perm:write
UpdateStorageDealStatus //perm:write
MarketAddBalance //perm:sign
MarketGetReserved //perm:sign
MarketReserveFunds //perm:sign
MarketReleaseFunds //perm:sign
MarketWithdraw //perm:sign
MarketSetAsk //perm:admin
MarketSetRetrievalAsk //perm:admin
DealsImportData //perm:admin
OfflineDealImport //perm:admin
MarketDataTransferPath //perm:admin
MarketSetDataTransferPath //perm:admin
// 被删除接口
ImportV1Data //perm:admin