2024-06-27 09:23:13 +00:00
package modules
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
2024-10-22 15:48:56 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
2024-06-27 09:23:13 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
ffsidContract "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid/contract"
policyContact "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy/contract"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)
var registerUserCmd = & cobra . Command {
Use : "register-user" ,
Short : "Register user and add allowed policy to him" ,
Long : "Register user in FrostFSID contract and add allowed policies. This is need to get access to s3-gw operations." ,
Example : ` frostfs - s3 - authmate register - user -- wallet wallet . json -- rpc - endpoint http : //morph-chain.frostfs.devenv:30333
frostfs - s3 - authmate register - user -- wallet wallet . json -- contract - wallet contract - wallet . json -- rpc - endpoint http : //morph-chain.frostfs.devenv:30333 --namespace namespace --frostfsid-name devenv --frostfsid-contract frostfsid.frostfs --proxy-contract proxy.frostfs --policy-contract policy.frostfs`,
RunE : runRegisterUserCmd ,
}
const (
frostfsIDContractFlag = "frostfsid-contract"
proxyContractFlag = "proxy-contract"
usernameFlag = "username"
namespaceFlag = "namespace"
policyContractFlag = "policy-contract"
contractWalletFlag = "contract-wallet"
contractWalletAddressFlag = "contract-wallet-address"
rpcEndpointFlag = "rpc-endpoint"
)
const walletContractPassphraseCfg = "wallet.contract.passphrase"
func initRegisterUserCmd ( ) {
registerUserCmd . Flags ( ) . String ( walletFlag , "" , "Path to the wallet with account of the user that will be registered in FrostFS ID contract" )
registerUserCmd . Flags ( ) . String ( addressFlag , "" , "Address of the user wallet that will be registered in FrostFS ID contract" )
registerUserCmd . Flags ( ) . String ( frostfsIDContractFlag , "frostfsid.frostfs" , "FrostfsID contract hash (LE) or name in NNS to register public key in contract" )
registerUserCmd . Flags ( ) . String ( proxyContractFlag , "proxy.frostfs" , "Proxy contract hash (LE) or name in NNS to use when interact with frostfsid contract" )
registerUserCmd . Flags ( ) . String ( namespaceFlag , "" , "Namespace to register public key in frostfsid contract and add policy chains" )
registerUserCmd . Flags ( ) . String ( usernameFlag , "" , "Username to set for public key in frostfsid contract" )
registerUserCmd . Flags ( ) . String ( contractWalletFlag , "" , "Path to wallet that will be used to interact with contracts (if missing key from wallet flag be used)" )
registerUserCmd . Flags ( ) . String ( contractWalletAddressFlag , "" , "Address of the contract wallet account" )
registerUserCmd . Flags ( ) . String ( policyContractFlag , "policy.frostfs" , "Policy contract hash (LE) or name in NNS to save allowed chains for key" )
registerUserCmd . Flags ( ) . String ( rpcEndpointFlag , "" , "NEO node RPC address" )
_ = registerUserCmd . MarkFlagRequired ( walletFlag )
_ = registerUserCmd . MarkFlagRequired ( rpcEndpointFlag )
}
func runRegisterUserCmd ( cmd * cobra . Command , _ [ ] string ) error {
ctx , cancel := context . WithTimeout ( cmd . Context ( ) , viper . GetDuration ( timeoutFlag ) )
defer cancel ( )
log := getLogger ( )
key , contractKey , err := parseKeys ( )
if err != nil {
return wrapPreparationError ( err )
}
frostfsIDClient , err := initFrostFSIDContract ( ctx , log , contractKey )
if err != nil {
return wrapFrostFSIDInitError ( err )
}
if err = registerPublicKey ( log , frostfsIDClient , key . PublicKey ( ) ) ; err != nil {
return wrapBusinessLogicError ( err )
}
policyClient , err := initPolicyContract ( ctx , log , contractKey )
if err != nil {
return wrapPolicyInitError ( err )
}
if err = addAllowedPolicyChains ( cmd , log , policyClient , key . PublicKey ( ) ) ; err != nil {
return wrapBusinessLogicError ( err )
}
return nil
}
func parseKeys ( ) ( userKey * keys . PrivateKey , contractKey * keys . PrivateKey , err error ) {
password := wallet . GetPassword ( viper . GetViper ( ) , walletPassphraseCfg )
key , err := wallet . GetKeyFromPath ( viper . GetString ( walletFlag ) , viper . GetString ( addressFlag ) , password )
if err != nil {
return nil , nil , fmt . Errorf ( "failed to load frostfs private key: %s" , err )
}
contractKey = key
if contractWallet := viper . GetString ( contractWalletFlag ) ; contractWallet != "" {
password = wallet . GetPassword ( viper . GetViper ( ) , walletContractPassphraseCfg )
contractKey , err = wallet . GetKeyFromPath ( contractWallet , viper . GetString ( contractWalletAddressFlag ) , password )
if err != nil {
return nil , nil , fmt . Errorf ( "failed to load contract private key: %s" , err )
}
}
return key , contractKey , nil
}
func initFrostFSIDContract ( ctx context . Context , log * zap . Logger , key * keys . PrivateKey ) ( * ffsidContract . FrostFSID , error ) {
log . Debug ( logs . PrepareFrostfsIDClient )
cfg := ffsidContract . Config {
RPCAddress : viper . GetString ( rpcEndpointFlag ) ,
Contract : viper . GetString ( frostfsIDContractFlag ) ,
ProxyContract : viper . GetString ( proxyContractFlag ) ,
Key : key ,
2024-10-22 15:48:56 +00:00
Waiter : commonclient . WaiterOptions {
IgnoreAlreadyExistsError : false ,
VerifyExecResults : true ,
} ,
2024-06-27 09:23:13 +00:00
}
cli , err := ffsidContract . New ( ctx , cfg )
if err != nil {
return nil , fmt . Errorf ( "create frostfsid client: %w" , err )
}
return cli , nil
}
func registerPublicKey ( log * zap . Logger , cli * ffsidContract . FrostFSID , key * keys . PublicKey ) error {
namespace := viper . GetString ( namespaceFlag )
log . Debug ( logs . CreateSubjectInFrostFSID )
err := cli . Wait ( cli . CreateSubject ( namespace , key ) )
if err != nil {
if strings . Contains ( err . Error ( ) , "subject already exists" ) {
log . Debug ( logs . SubjectAlreadyExistsInFrostFSID , zap . String ( "address" , key . Address ( ) ) )
} else {
return fmt . Errorf ( "create subject in frostfsid: %w" , err )
}
}
name := viper . GetString ( usernameFlag )
if name == "" {
return nil
}
log . Debug ( logs . SetSubjectNameInFrostFSID )
if err = cli . Wait ( cli . SetSubjectName ( key , name ) ) ; err != nil {
return fmt . Errorf ( "set subject name in frostfsid: %w" , err )
}
return nil
}
func initPolicyContract ( ctx context . Context , log * zap . Logger , key * keys . PrivateKey ) ( * policyContact . Client , error ) {
log . Debug ( logs . PreparePolicyClient )
cfg := policyContact . Config {
RPCAddress : viper . GetString ( rpcEndpointFlag ) ,
Contract : viper . GetString ( policyContractFlag ) ,
ProxyContract : viper . GetString ( proxyContractFlag ) ,
Key : key ,
}
cli , err := policyContact . New ( ctx , cfg )
if err != nil {
return nil , fmt . Errorf ( "create policy client: %w" , err )
}
return cli , nil
}
func addAllowedPolicyChains ( cmd * cobra . Command , log * zap . Logger , cli * policyContact . Client , key * keys . PublicKey ) error {
log . Debug ( logs . AddPolicyChainRules )
namespace := viper . GetString ( namespaceFlag )
allowAllRule := chain . Rule {
Status : chain . Allow ,
Actions : chain . Actions { Names : [ ] string { "*" } } ,
Resources : chain . Resources { Names : [ ] string { "*" } } ,
}
chains := [ ] * chain . Chain {
{ ID : chain . ID ( chain . S3 + ":authmate" ) , Rules : [ ] chain . Rule { allowAllRule } } ,
{ ID : chain . ID ( chain . Ingress + ":authmate" ) , Rules : [ ] chain . Rule { allowAllRule } } ,
}
kind := policy . Kind ( policy . User )
entity := namespace + ":" + key . Address ( )
tx := cli . StartTx ( )
for _ , ch := range chains {
tx . AddChain ( kind , entity , ch . ID , ch . Bytes ( ) )
}
if err := cli . SendTx ( tx ) ; err != nil {
return fmt . Errorf ( "add policy chain: %w" , err )
}
cmd . Printf ( "Added policy rules:\nkind: '%c'\nentity: '%s'\nchains:\n" , kind , entity )
enc := json . NewEncoder ( os . Stdout )
return enc . Encode ( chains )
}