diff --git a/authmate/authmate.go b/authmate/authmate.go index f0ab58d..60e2586 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -81,6 +81,11 @@ type FrostFS interface { TimeToEpoch(context.Context, time.Time) (uint64, uint64, error) } +// FrostFSID represents interface to interact with frostfsid contract. +type FrostFSID interface { + RegisterPublicKey(*keys.PublicKey) error +} + // Agent contains client communicating with FrostFS and logger. type Agent struct { frostFS FrostFS diff --git a/cmd/s3-authmate/modules/errors.go b/cmd/s3-authmate/modules/errors.go index 647071c..b92f7f0 100644 --- a/cmd/s3-authmate/modules/errors.go +++ b/cmd/s3-authmate/modules/errors.go @@ -12,6 +12,10 @@ type ( businessLogicError struct { err error } + + frostFSIDInitError struct { + err error + } ) func wrapPreparationError(e error) error { @@ -38,6 +42,14 @@ func (e businessLogicError) Error() string { return e.err.Error() } +func wrapFrostFSIDInitError(e error) error { + return frostFSIDInitError{e} +} + +func (e frostFSIDInitError) Error() string { + return e.err.Error() +} + // ExitCode picks corresponding error code depending on the type of error provided. // Returns 1 if error type is unknown. func ExitCode(e error) int { @@ -48,6 +60,8 @@ func ExitCode(e error) int { return 3 case businessLogicError: return 4 + case frostFSIDInitError: + return 4 } return 1 } diff --git a/cmd/s3-authmate/modules/issue-secret.go b/cmd/s3-authmate/modules/issue-secret.go index 3d62d0e..38cb464 100644 --- a/cmd/s3-authmate/modules/issue-secret.go +++ b/cmd/s3-authmate/modules/issue-secret.go @@ -8,6 +8,7 @@ import ( "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -37,6 +38,8 @@ const ( lifetimeFlag = "lifetime" containerPolicyFlag = "container-policy" awsCLICredentialFlag = "aws-cli-credentials" + frostfsIDFlag = "frostfsid" + rpcEndpointFlag = "rpc-endpoint" ) const ( @@ -77,6 +80,8 @@ func initIssueSecretCmd() { issueSecretCmd.Flags().Duration(poolHealthcheckTimeoutFlag, defaultPoolHealthcheckTimeout, "Timeout for request to node to decide if it is alive") issueSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status") issueSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC") + issueSecretCmd.Flags().String(frostfsIDFlag, "", "FrostfsID contract hash (LE) or name in NNS to register public key in contract (rpc-endpoint flag also must be provided)") + issueSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address") _ = issueSecretCmd.MarkFlagRequired(walletFlag) _ = issueSecretCmd.MarkFlagRequired(peerFlag) @@ -152,6 +157,28 @@ func runIssueSecretCmd(cmd *cobra.Command, _ []string) error { return wrapFrostFSInitError(fmt.Errorf("failed to create FrostFS component: %s", err)) } + frostFSID := viper.GetString(frostfsIDFlag) + if frostFSID != "" { + rpcAddress := viper.GetString(rpcEndpointFlag) + if rpcAddress == "" { + return wrapPreparationError(fmt.Errorf("you can use '%s' flag only along with '%s'", frostfsIDFlag, rpcEndpointFlag)) + } + cfg := frostfsid.Config{ + RPCAddress: rpcAddress, + Contract: frostFSID, + Key: key, + } + + frostfsIDClient, err := createFrostFSID(ctx, log, cfg) + if err != nil { + return wrapFrostFSIDInitError(err) + } + + if err = frostfsIDClient.RegisterPublicKey(key.PublicKey()); err != nil { + return wrapBusinessLogicError(fmt.Errorf("failed to register key in frostfsid: %w", err)) + } + } + issueSecretOptions := &authmate.IssueSecretOptions{ Container: authmate.ContainerOptions{ ID: cnrID, diff --git a/cmd/s3-authmate/modules/update-secret.go b/cmd/s3-authmate/modules/update-secret.go index e79cd02..b68bb97 100644 --- a/cmd/s3-authmate/modules/update-secret.go +++ b/cmd/s3-authmate/modules/update-secret.go @@ -7,6 +7,7 @@ import ( "strings" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -39,6 +40,8 @@ func initUpdateSecretCmd() { updateSecretCmd.Flags().Duration(poolHealthcheckTimeoutFlag, defaultPoolHealthcheckTimeout, "Timeout for request to node to decide if it is alive") updateSecretCmd.Flags().Duration(poolRebalanceIntervalFlag, defaultPoolRebalanceInterval, "Interval for updating nodes health status") updateSecretCmd.Flags().Duration(poolStreamTimeoutFlag, defaultPoolStreamTimeout, "Timeout for individual operation in streaming RPC") + updateSecretCmd.Flags().String(frostfsIDFlag, "", "FrostfsID contract hash (LE) or name in NNS to register public key in contract (rpc-endpoint flag also must be provided)") + updateSecretCmd.Flags().String(rpcEndpointFlag, "", "NEO node RPC address") _ = updateSecretCmd.MarkFlagRequired(walletFlag) _ = updateSecretCmd.MarkFlagRequired(peerFlag) @@ -94,6 +97,28 @@ func runUpdateSecretCmd(cmd *cobra.Command, _ []string) error { return wrapFrostFSInitError(fmt.Errorf("failed to create FrostFS component: %s", err)) } + frostFSID := viper.GetString(frostfsIDFlag) + if frostFSID != "" { + rpcAddress := viper.GetString(rpcEndpointFlag) + if rpcAddress == "" { + return wrapPreparationError(fmt.Errorf("you can use '%s' flag only along with '%s'", frostfsIDFlag, rpcEndpointFlag)) + } + cfg := frostfsid.Config{ + RPCAddress: rpcAddress, + Contract: frostFSID, + Key: key, + } + + frostfsIDClient, err := createFrostFSID(ctx, log, cfg) + if err != nil { + return wrapFrostFSIDInitError(err) + } + + if err = frostfsIDClient.RegisterPublicKey(key.PublicKey()); err != nil { + return wrapBusinessLogicError(fmt.Errorf("failed to register key in frostfsid: %w", err)) + } + } + updateSecretOptions := &authmate.UpdateSecretOptions{ Address: accessBoxAddress, FrostFSKey: key, diff --git a/cmd/s3-authmate/modules/utils.go b/cmd/s3-authmate/modules/utils.go index 807b7b2..e43ef27 100644 --- a/cmd/s3-authmate/modules/utils.go +++ b/cmd/s3-authmate/modules/utils.go @@ -10,6 +10,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -141,3 +142,14 @@ func getLogger() *zap.Logger { return log } + +func createFrostFSID(ctx context.Context, log *zap.Logger, cfg frostfsid.Config) (authmate.FrostFSID, error) { + log.Debug(logs.PrepareFrostfsIDClient) + + cli, err := frostfsid.New(ctx, cfg) + if err != nil { + return nil, fmt.Errorf("create frostfsid client: %w", err) + } + + return cli, nil +} diff --git a/docs/authmate.md b/docs/authmate.md index be9a99c..b9fbed5 100644 --- a/docs/authmate.md +++ b/docs/authmate.md @@ -143,6 +143,9 @@ the secret. Format of `access_key_id`: `%cid0%oid`, where 0(zero) is a delimiter * `--access-key-id` -- credentials that you want to update (e.g. to add more gates that can use your creds) without changing values of `aws_access_key_id` and `aws_secret_access_key`. If you want to update credential you MUST provide also secret key using `AUTHMATE_SECRET_ACCESS_KEY` env variable. +* `--frostfsid` -- FrostfsID contract hash (LE) or name in NNS to register public key in contract +(`--rpc-endpoint` flag also must be provided). +* `--rpc-endpoint` -- NEO node RPC address. ### Bearer tokens diff --git a/internal/frostfs/frostfsid/frostfsid.go b/internal/frostfs/frostfsid/frostfsid.go index cc3db75..6979b73 100644 --- a/internal/frostfs/frostfsid/frostfsid.go +++ b/internal/frostfs/frostfsid/frostfsid.go @@ -7,6 +7,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -31,7 +32,10 @@ type Config struct { Key *keys.PrivateKey } -var _ middleware.FrostFSID = (*FrostFSID)(nil) +var ( + _ middleware.FrostFSID = (*FrostFSID)(nil) + _ authmate.FrostFSID = (*FrostFSID)(nil) +) // New creates new FrostfsID contract wrapper that implements auth.FrostFSID interface. func New(ctx context.Context, cfg Config) (*FrostFSID, error) { @@ -67,6 +71,15 @@ func (f *FrostFSID) ValidatePublicKey(key *keys.PublicKey) error { return err } +func (f *FrostFSID) RegisterPublicKey(key *keys.PublicKey) error { + _, err := f.cli.Wait(f.cli.CreateSubject(key)) + if err != nil && !strings.Contains(err.Error(), "subject already exists") { + return err + } + + return nil +} + func fetchContractHash(cfg Config) (util.Uint160, error) { if hash, err := util.Uint160DecodeStringLE(cfg.Contract); err == nil { return hash, nil diff --git a/internal/logs/logs.go b/internal/logs/logs.go index cd89684..20daaa5 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -45,6 +45,7 @@ const ( SkipEmptyAddress = "skip, empty address" // Warn in ../../cmd/s3-gw/app_settings.go AddedStoragePeer = "added storage peer" // Info in ../../cmd/s3-gw/app_settings.go PrepareConnectionPool = "prepare connection pool" // Debug in ../../cmd/s3-authmate/modules/utils.go + PrepareFrostfsIDClient = "prepare frostfsid client" // Debug in ../../cmd/s3-authmate/modules/utils.go InvalidCacheEntryType = "invalid cache entry type" // Warn in ../../api/cache/* InvalidCacheKeyType = "invalid cache key type" // Warn in ../../api/cache/objectslist.go ObjectIsCopied = "object is copied" // Info in ../../api/handler/copy.go