forked from TrueCloudLab/frostfs-s3-gw
Denis Kirillov
70eedfc077
New command allows register user in frostfsid and set allowed rules in policy contract Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
202 lines
7.4 KiB
Go
202 lines
7.4 KiB
Go
package modules
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"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,
|
|
}
|
|
|
|
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)
|
|
}
|