diff --git a/cmd/neofs-cli/modules/token/create.go b/cmd/neofs-cli/modules/token/create.go index 1f9489d8a..23bb170c3 100644 --- a/cmd/neofs-cli/modules/token/create.go +++ b/cmd/neofs-cli/modules/token/create.go @@ -1,10 +1,19 @@ package token import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" "encoding/json" "fmt" "io/ioutil" + "strconv" + "time" + "github.com/nspcc-dev/neofs-node/pkg/network" + "github.com/nspcc-dev/neofs-sdk-go/client" eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/owner" "github.com/nspcc-dev/neofs-sdk-go/token" @@ -19,22 +28,30 @@ const ( ownerFlag = "owner" outFlag = "out" jsonFlag = "json" + rpcFlag = "rpc-endpoint" ) var createCmd = &cobra.Command{ Use: "create", Short: "Create bearer token", - RunE: createToken, + Long: `Create bearer token. + +All epoch flags can be specified relative to the current epoch with the +n syntax. +In this case --` + rpcFlag + ` flag should be specified and the epoch in bearer token +is set to current epoch + n. +`, + RunE: createToken, } func init() { createCmd.Flags().StringP(eaclFlag, "e", "", "path to the extended ACL table") - createCmd.Flags().Uint64P(issuedAtFlag, "i", 0, "epoch to issue token at") - createCmd.Flags().Uint64P(notValidBeforeFlag, "n", 0, "not valid before epoch") - createCmd.Flags().Uint64P(expireAtFlag, "x", 0, "expiration epoch") + createCmd.Flags().StringP(issuedAtFlag, "i", "", "epoch to issue token at") + createCmd.Flags().StringP(notValidBeforeFlag, "n", "", "not valid before epoch") + createCmd.Flags().StringP(expireAtFlag, "x", "", "expiration epoch") createCmd.Flags().StringP(ownerFlag, "o", "", "token owner") createCmd.Flags().String(outFlag, "", "file to write token to") createCmd.Flags().Bool(jsonFlag, false, "output token in JSON") + createCmd.Flags().StringP(rpcFlag, "r", "", "rpc-endpoint") _ = cobra.MarkFlagFilename(createCmd.Flags(), eaclFlag) @@ -46,9 +63,34 @@ func init() { } func createToken(cmd *cobra.Command, _ []string) error { - iat, _ := cmd.Flags().GetUint64(issuedAtFlag) - exp, _ := cmd.Flags().GetUint64(expireAtFlag) - nvb, _ := cmd.Flags().GetUint64(notValidBeforeFlag) + iat, iatRelative, err := parseEpoch(cmd, issuedAtFlag) + if err != nil { + return err + } + exp, expRelative, err := parseEpoch(cmd, expireAtFlag) + if err != nil { + return err + } + nvb, nvbRelative, err := parseEpoch(cmd, notValidBeforeFlag) + if err != nil { + return err + } + if iatRelative || expRelative || nvbRelative { + endpoint, _ := cmd.Flags().GetString(rpcFlag) + currEpoch, err := getCurrentEpoch(endpoint) + if err != nil { + return err + } + if iatRelative { + iat += currEpoch + } + if expRelative { + exp += currEpoch + } + if nvbRelative { + nvb += currEpoch + } + } if exp < nvb { return fmt.Errorf("expiration epoch is less than not-valid-before epoch: %d < %d", exp, nvb) } @@ -77,7 +119,6 @@ func createToken(cmd *cobra.Command, _ []string) error { } var data []byte - var err error toJSON, _ := cmd.Flags().GetBool(jsonFlag) if toJSON { @@ -96,3 +137,65 @@ func createToken(cmd *cobra.Command, _ []string) error { return nil } + +// parseEpoch parses epoch argument. Second return value is true if +// the specified epoch is relative, and false otherwise. +func parseEpoch(cmd *cobra.Command, flag string) (uint64, bool, error) { + s, _ := cmd.Flags().GetString(flag) + if len(s) == 0 { + return 0, false, nil + } + + relative := s[0] == '+' + if relative { + s = s[1:] + } + + epoch, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return 0, relative, fmt.Errorf("can't parse epoch for %s argument: %w", flag, err) + } + return epoch, relative, nil +} + +// getCurrentEpoch returns current epoch. +func getCurrentEpoch(endpoint string) (uint64, error) { + var ( + c client.Client + prmInit client.PrmInit + prmDial client.PrmDial + addr network.Address + ) + + if err := addr.FromString(endpoint); err != nil { + return 0, fmt.Errorf("can't parse RPC endpoint: %w", err) + } + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return 0, fmt.Errorf("can't generate key to sign query: %w", err) + } + + prmInit.SetDefaultPrivateKey(*key) + prmInit.ResolveNeoFSFailures() + c.Init(prmInit) + + prmDial.SetServerURI(addr.HostAddr()) + if addr.TLSEnabled() { + prmDial.SetTLSConfig(&tls.Config{}) + } + + if err := c.Dial(prmDial); err != nil { + return 0, fmt.Errorf("can't initialize client: %w", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + ni, err := c.NetworkInfo(ctx, client.PrmNetworkInfo{}) + if err != nil { + return 0, err + } + + return ni.Info().CurrentEpoch(), nil +}