diff --git a/cmd/neofs-cli/internal/common/eacl.go b/cmd/neofs-cli/internal/common/eacl.go new file mode 100644 index 0000000000..052eb69200 --- /dev/null +++ b/cmd/neofs-cli/internal/common/eacl.go @@ -0,0 +1,49 @@ +package common + +import ( + "errors" + "os" + + "github.com/nspcc-dev/neofs-node/pkg/core/version" + "github.com/nspcc-dev/neofs-sdk-go/eacl" + versionSDK "github.com/nspcc-dev/neofs-sdk-go/version" + "github.com/spf13/cobra" +) + +var errUnsupportedEACLFormat = errors.New("unsupported eACL format") + +// ReadEACL reads extended ACL table from eaclPath. +func ReadEACL(cmd *cobra.Command, eaclPath string) *eacl.Table { + _, err := os.Stat(eaclPath) // check if `eaclPath` is an existing file + if err != nil { + ExitOnErr(cmd, "", errors.New("incorrect path to file with EACL")) + } + + PrintVerbose("Reading EACL from file: %s", eaclPath) + + data, err := os.ReadFile(eaclPath) + ExitOnErr(cmd, "can't read file with EACL: %w", err) + + table := eacl.NewTable() + + if err = table.UnmarshalJSON(data); err == nil { + validateAndFixEACLVersion(table) + PrintVerbose("Parsed JSON encoded EACL table") + return table + } + + if err = table.Unmarshal(data); err == nil { + validateAndFixEACLVersion(table) + PrintVerbose("Parsed binary encoded EACL table") + return table + } + + ExitOnErr(cmd, "", errUnsupportedEACLFormat) + return nil +} + +func validateAndFixEACLVersion(table *eacl.Table) { + if !version.IsValid(table.Version()) { + table.SetVersion(versionSDK.Current()) + } +} diff --git a/cmd/neofs-cli/internal/common/token.go b/cmd/neofs-cli/internal/common/token.go new file mode 100644 index 0000000000..654c34c22d --- /dev/null +++ b/cmd/neofs-cli/internal/common/token.go @@ -0,0 +1,57 @@ +package common + +import ( + "os" + + "github.com/nspcc-dev/neofs-sdk-go/bearer" + "github.com/nspcc-dev/neofs-sdk-go/session" + "github.com/spf13/cobra" +) + +// ReadBearerToken reads bearer token from the path provided in a specified flag. +func ReadBearerToken(cmd *cobra.Command, flagname string) *bearer.Token { + path, err := cmd.Flags().GetString(flagname) + ExitOnErr(cmd, "", err) + + if len(path) == 0 { + return nil + } + + data, err := os.ReadFile(path) + ExitOnErr(cmd, "can't read bearer token file: %w", err) + + var tok bearer.Token + if err := tok.UnmarshalJSON(data); err != nil { + err = tok.Unmarshal(data) + ExitOnErr(cmd, "can't decode bearer token: %w", err) + + PrintVerbose("Using binary encoded bearer token") + } else { + PrintVerbose("Using JSON encoded bearer token") + } + + return &tok +} + +// ReadSessionToken reads session token as JSON file with session token +// from path provided in a specified flag. +func ReadSessionToken(cmd *cobra.Command, flag string) *session.Token { + // try to read session token from file + var tok *session.Token + + path, err := cmd.Flags().GetString(flag) + ExitOnErr(cmd, "", err) + + if path == "" { + return tok + } + + data, err := os.ReadFile(path) + ExitOnErr(cmd, "could not open file with session token: %w", err) + + tok = session.NewToken() + err = tok.UnmarshalJSON(data) + ExitOnErr(cmd, "could not ummarshal session token from file: %w", err) + + return tok +} diff --git a/cmd/neofs-cli/modules/container.go b/cmd/neofs-cli/modules/container.go index c6c67d5ed4..5eba8f9ecd 100644 --- a/cmd/neofs-cli/modules/container.go +++ b/cmd/neofs-cli/modules/container.go @@ -16,7 +16,6 @@ import ( "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" - "github.com/nspcc-dev/neofs-node/pkg/core/version" "github.com/nspcc-dev/neofs-sdk-go/acl" "github.com/nspcc-dev/neofs-sdk-go/container" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" @@ -25,7 +24,6 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/object" addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address" "github.com/nspcc-dev/neofs-sdk-go/policy" - "github.com/nspcc-dev/neofs-sdk-go/session" subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id" "github.com/nspcc-dev/neofs-sdk-go/user" versionSDK "github.com/nspcc-dev/neofs-sdk-go/version" @@ -91,10 +89,9 @@ var ( ) var ( - errDeleteTimeout = errors.New("timeout: container has not been removed from sidechain") - errCreateTimeout = errors.New("timeout: container has not been persisted on sidechain") - errSetEACLTimeout = errors.New("timeout: EACL has not been persisted on sidechain") - errUnsupportedEACLFormat = errors.New("unsupported eACL format") + errDeleteTimeout = errors.New("timeout: container has not been removed from sidechain") + errCreateTimeout = errors.New("timeout: container has not been persisted on sidechain") + errSetEACLTimeout = errors.New("timeout: EACL has not been persisted on sidechain") ) // containerCmd represents the container command @@ -161,9 +158,7 @@ It will be stored in sidechain when inner ring will accepts it.`, nonce, err := parseNonce(containerNonce) common.ExitOnErr(cmd, "", err) - tok, err := getSessionToken(sessionTokenPath) - common.ExitOnErr(cmd, "", err) - + tok := common.ReadSessionToken(cmd, sessionTokenFlag) key := key.GetOrGenerate(cmd) var idOwner *user.ID @@ -228,8 +223,7 @@ Only owner of the container has a permission to remove container.`, id, err := parseContainerID(containerID) common.ExitOnErr(cmd, "", err) - tok, err := getSessionToken(sessionTokenPath) - common.ExitOnErr(cmd, "", err) + tok := common.ReadSessionToken(cmd, sessionTokenFlag) var ( delPrm internalclient.DeleteContainerPrm @@ -416,11 +410,9 @@ Container ID in EACL table will be substituted with ID from the CLI.`, id, err := parseContainerID(containerID) common.ExitOnErr(cmd, "", err) - eaclTable, err := parseEACL(eaclPathFrom) - common.ExitOnErr(cmd, "", err) + eaclTable := common.ReadEACL(cmd, eaclPathFrom) - tok, err := getSessionToken(sessionTokenPath) - common.ExitOnErr(cmd, "", err) + tok := common.ReadSessionToken(cmd, sessionTokenFlag) eaclTable.SetCID(*id) eaclTable.SetSessionToken(tok) @@ -591,26 +583,6 @@ func init() { } } -// getSessionToken reads `` as JSON file with session token and parses it. -func getSessionToken(path string) (*session.Token, error) { - // try to read session token from file - var tok *session.Token - - if path != "" { - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("could not open file with session token: %w", err) - } - - tok = session.NewToken() - if err = tok.UnmarshalJSON(data); err != nil { - return nil, fmt.Errorf("could not ummarshal session token from file: %w", err) - } - } - - return tok, nil -} - func prettyPrintContainerList(cmd *cobra.Command, list []cid.ID) { for i := range list { cmd.Println(list[i].String()) @@ -787,42 +759,6 @@ func prettyPrintContainer(cmd *cobra.Command, cnr *container.Container, jsonEnco cmd.Println(strings.Join(policy.Encode(cnr.PlacementPolicy()), "\n")) } -func parseEACL(eaclPath string) (*eacl.Table, error) { - _, err := os.Stat(eaclPath) // check if `eaclPath` is an existing file - if err != nil { - return nil, errors.New("incorrect path to file with EACL") - } - - common.PrintVerbose("Reading EACL from file: %s", eaclPath) - - data, err := os.ReadFile(eaclPath) - if err != nil { - return nil, fmt.Errorf("can't read file with EACL: %w", err) - } - - table := eacl.NewTable() - - if err = table.UnmarshalJSON(data); err == nil { - validateAndFixEACLVersion(table) - common.PrintVerbose("Parsed JSON encoded EACL table") - return table, nil - } - - if err = table.Unmarshal(data); err == nil { - validateAndFixEACLVersion(table) - common.PrintVerbose("Parsed binary encoded EACL table") - return table, nil - } - - return nil, errUnsupportedEACLFormat -} - -func validateAndFixEACLVersion(table *eacl.Table) { - if !version.IsValid(table.Version()) { - table.SetVersion(versionSDK.Current()) - } -} - func prettyPrintEACL(cmd *cobra.Command, table *eacl.Table) { common.PrettyPrintJSON(cmd, table, "eACL") } @@ -837,3 +773,14 @@ func prettyPrintBasicACL(cmd *cobra.Command, basicACL acl.BasicACL) { } cmd.Println() } + +func prettyPrintUnixTime(s string) string { + unixTime, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return "malformed" + } + + timestamp := time.Unix(unixTime, 0) + + return timestamp.String() +} diff --git a/cmd/neofs-cli/modules/object.go b/cmd/neofs-cli/modules/object.go index 9f81a41062..87b5ad4a8f 100644 --- a/cmd/neofs-cli/modules/object.go +++ b/cmd/neofs-cli/modules/object.go @@ -22,7 +22,6 @@ import ( "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" sessionCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/session" - "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/checksum" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -1110,31 +1109,6 @@ func marshalHeader(cmd *cobra.Command, hdr *object.Object) ([]byte, error) { } } -func getBearerToken(cmd *cobra.Command, flagname string) (*bearer.Token, error) { - path, err := cmd.Flags().GetString(flagname) - if err != nil || len(path) == 0 { - return nil, nil - } - - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("can't read bearer token file: %w", err) - } - - var tok bearer.Token - if err := tok.UnmarshalJSON(data); err != nil { - if err = tok.Unmarshal(data); err != nil { - return nil, fmt.Errorf("can't decode bearer token: %w", err) - } - - common.PrintVerbose("Using binary encoded bearer token") - } else { - common.PrintVerbose("Using JSON encoded bearer token") - } - - return &tok, nil -} - func getObjectRange(cmd *cobra.Command, _ []string) { objAddr, err := getObjectAddress(cmd) common.ExitOnErr(cmd, "", err) diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index e0fce317b1..5ca5486a50 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -17,6 +17,7 @@ import ( bearerCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/bearer" controlCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/control" sessionCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/session" + utilCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util" "github.com/nspcc-dev/neofs-node/misc" "github.com/nspcc-dev/neofs-node/pkg/util/gendoc" "github.com/nspcc-dev/neofs-sdk-go/bearer" @@ -95,6 +96,7 @@ func init() { rootCmd.AddCommand(sessionCli.Cmd) rootCmd.AddCommand(accountingCli.Cmd) rootCmd.AddCommand(controlCli.Cmd) + rootCmd.AddCommand(utilCli.Cmd) rootCmd.AddCommand(gendoc.Command(rootCmd)) } @@ -164,8 +166,7 @@ type bearerPrm interface { } func prepareBearerPrm(cmd *cobra.Command, prm bearerPrm) { - btok, err := getBearerToken(cmd, bearerTokenFlag) - common.ExitOnErr(cmd, "bearer token: %w", err) + btok := common.ReadBearerToken(cmd, bearerTokenFlag) prm.SetBearerToken(btok) } diff --git a/cmd/neofs-cli/modules/util.go b/cmd/neofs-cli/modules/util.go deleted file mode 100644 index 7914b981a3..0000000000 --- a/cmd/neofs-cli/modules/util.go +++ /dev/null @@ -1,467 +0,0 @@ -package cmd - -import ( - "bytes" - "crypto/rand" - "encoding/json" - "errors" - "fmt" - "os" - "strconv" - "time" - - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" - "github.com/nspcc-dev/neofs-node/pkg/util/keyer" - locodedb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db" - airportsdb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/airports" - locodebolt "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/boltdb" - continentsdb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/continents/geojson" - csvlocode "github.com/nspcc-dev/neofs-node/pkg/util/locode/table/csv" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var errKeyerSingleArgument = errors.New("pass only one argument at a time") - -var ( - utilCmd = &cobra.Command{ - Use: "util", - Short: "Utility operations", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - flags := cmd.Flags() - - _ = viper.BindPFlag(commonflags.GenerateKey, flags.Lookup(commonflags.GenerateKey)) - _ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath)) - _ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account)) - }, - } - - signCmd = &cobra.Command{ - Use: "sign", - Short: "Sign NeoFS structure", - } - - signBearerCmd = &cobra.Command{ - Use: "bearer-token", - Short: "Sign bearer token to use it in requests", - Run: signBearerToken, - } - - signSessionCmd = &cobra.Command{ - Use: "session-token", - Short: "Sign session token to use it in requests", - Run: signSessionToken, - } - - convertCmd = &cobra.Command{ - Use: "convert", - Short: "Convert representation of NeoFS structures", - } - - convertEACLCmd = &cobra.Command{ - Use: "eacl", - Short: "Convert representation of extended ACL table", - Run: convertEACLTable, - } - - keyerCmd = &cobra.Command{ - Use: "keyer", - Short: "Generate or print information about keys", - Run: processKeyer, - } -) - -// locode section -var locodeCmd = &cobra.Command{ - Use: "locode", - Short: "Working with NeoFS UN/LOCODE database", -} - -const ( - locodeGenerateInputFlag = "in" - locodeGenerateSubDivFlag = "subdiv" - locodeGenerateAirportsFlag = "airports" - locodeGenerateCountriesFlag = "countries" - locodeGenerateContinentsFlag = "continents" - locodeGenerateOutputFlag = "out" -) - -type namesDB struct { - *airportsdb.DB - *csvlocode.Table -} - -var ( - locodeGenerateInPaths []string - locodeGenerateSubDivPath string - locodeGenerateAirportsPath string - locodeGenerateCountriesPath string - locodeGenerateContinentsPath string - locodeGenerateOutPath string - - locodeGenerateCmd = &cobra.Command{ - Use: "generate", - Short: "generate UN/LOCODE database for NeoFS", - Run: func(cmd *cobra.Command, _ []string) { - locodeDB := csvlocode.New( - csvlocode.Prm{ - Path: locodeGenerateInPaths[0], - SubDivPath: locodeGenerateSubDivPath, - }, - csvlocode.WithExtraPaths(locodeGenerateInPaths[1:]...), - ) - - airportDB := airportsdb.New(airportsdb.Prm{ - AirportsPath: locodeGenerateAirportsPath, - CountriesPath: locodeGenerateCountriesPath, - }) - - continentsDB := continentsdb.New(continentsdb.Prm{ - Path: locodeGenerateContinentsPath, - }) - - targetDB := locodebolt.New(locodebolt.Prm{ - Path: locodeGenerateOutPath, - }) - - err := targetDB.Open() - common.ExitOnErr(cmd, "", err) - - defer targetDB.Close() - - names := &namesDB{ - DB: airportDB, - Table: locodeDB, - } - - err = locodedb.FillDatabase(locodeDB, airportDB, continentsDB, names, targetDB) - common.ExitOnErr(cmd, "", err) - }, - } -) - -const ( - locodeInfoDBFlag = "db" - locodeInfoCodeFlag = "locode" -) - -var ( - locodeInfoDBPath string - locodeInfoCode string - - locodeInfoCmd = &cobra.Command{ - Use: "info", - Short: "print information about UN/LOCODE from NeoFS database", - Run: func(cmd *cobra.Command, _ []string) { - targetDB := locodebolt.New(locodebolt.Prm{ - Path: locodeInfoDBPath, - }, locodebolt.ReadOnly()) - - err := targetDB.Open() - common.ExitOnErr(cmd, "", err) - - defer targetDB.Close() - - record, err := locodedb.LocodeRecord(targetDB, locodeInfoCode) - common.ExitOnErr(cmd, "", err) - - cmd.Printf("Country: %s\n", record.CountryName()) - cmd.Printf("Location: %s\n", record.LocationName()) - cmd.Printf("Continent: %s\n", record.Continent()) - if subDivCode := record.SubDivCode(); subDivCode != "" { - cmd.Printf("Subdivision: [%s] %s\n", subDivCode, record.SubDivName()) - } - - geoPoint := record.GeoPoint() - cmd.Printf("Coordinates: %0.2f, %0.2f\n", geoPoint.Latitude(), geoPoint.Longitude()) - }, - } -) - -func initUtilKeyerCmd() { - keyerCmd.Flags().BoolP("generate", "g", false, "generate new private key") - keyerCmd.Flags().Bool("hex", false, "print all values in hex encoding") - keyerCmd.Flags().BoolP("uncompressed", "u", false, "use uncompressed public key format") - keyerCmd.Flags().BoolP("multisig", "m", false, "calculate multisig address from public keys") -} - -func initUtilSignBearerCmd() { - commonflags.InitWithoutRPC(signBearerCmd) - - flags := signBearerCmd.Flags() - - flags.String("from", "", "File with JSON or binary encoded bearer token to sign") - _ = signBearerCmd.MarkFlagFilename("from") - _ = signBearerCmd.MarkFlagRequired("from") - - flags.String("to", "", "File to dump signed bearer token (default: binary encoded)") - flags.Bool("json", false, "Dump bearer token in JSON encoding") -} - -func initUtilSignSessionCmd() { - commonflags.InitWithoutRPC(signSessionCmd) - - flags := signSessionCmd.Flags() - - flags.String("from", "", "File with JSON encoded session token to sign") - _ = signSessionCmd.MarkFlagFilename("from") - _ = signSessionCmd.MarkFlagRequired("from") - - flags.String("to", "", "File to save signed session token (optional)") -} - -func initUtilConvertEACLCmd() { - flags := convertEACLCmd.Flags() - - flags.String("from", "", "File with JSON or binary encoded extended ACL table") - _ = convertEACLCmd.MarkFlagFilename("from") - _ = convertEACLCmd.MarkFlagRequired("from") - - flags.String("to", "", "File to dump extended ACL table (default: binary encoded)") - flags.Bool("json", false, "Dump extended ACL table in JSON encoding") -} - -func initUtilLocodeGenerateCmd() { - flags := locodeGenerateCmd.Flags() - - flags.StringSliceVar(&locodeGenerateInPaths, locodeGenerateInputFlag, nil, "List of paths to UN/LOCODE tables (csv)") - _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateInputFlag) - - flags.StringVar(&locodeGenerateSubDivPath, locodeGenerateSubDivFlag, "", "Path to UN/LOCODE subdivision database (csv)") - _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateSubDivFlag) - - flags.StringVar(&locodeGenerateAirportsPath, locodeGenerateAirportsFlag, "", "Path to OpenFlights airport database (csv)") - _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateAirportsFlag) - - flags.StringVar(&locodeGenerateCountriesPath, locodeGenerateCountriesFlag, "", "Path to OpenFlights country database (csv)") - _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateCountriesFlag) - - flags.StringVar(&locodeGenerateContinentsPath, locodeGenerateContinentsFlag, "", "Path to continent polygons (GeoJSON)") - _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateContinentsFlag) - - flags.StringVar(&locodeGenerateOutPath, locodeGenerateOutputFlag, "", "Target path for generated database") - _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateOutputFlag) -} - -func initUtilLocodeInfoCmd() { - flags := locodeInfoCmd.Flags() - - flags.StringVar(&locodeInfoDBPath, locodeInfoDBFlag, "", "Path to NeoFS UN/LOCODE database") - _ = locodeInfoCmd.MarkFlagRequired(locodeInfoDBFlag) - - flags.StringVar(&locodeInfoCode, locodeInfoCodeFlag, "", "UN/LOCODE") - _ = locodeInfoCmd.MarkFlagRequired(locodeInfoCodeFlag) -} - -func init() { - rootCmd.AddCommand(utilCmd) - - utilCmd.AddCommand( - signCmd, - convertCmd, - keyerCmd, - locodeCmd, - ) - - signCmd.AddCommand(signBearerCmd, signSessionCmd) - convertCmd.AddCommand(convertEACLCmd) - locodeCmd.AddCommand(locodeGenerateCmd, locodeInfoCmd) - - initUtilKeyerCmd() - - initUtilSignBearerCmd() - initUtilSignSessionCmd() - - initUtilConvertEACLCmd() - - initUtilLocodeInfoCmd() - initUtilLocodeGenerateCmd() -} - -func signBearerToken(cmd *cobra.Command, _ []string) { - btok, err := getBearerToken(cmd, "from") - common.ExitOnErr(cmd, "", err) - - pk := key.GetOrGenerate(cmd) - - err = btok.Sign(*pk) - common.ExitOnErr(cmd, "", err) - - to := cmd.Flag("to").Value.String() - jsonFlag, _ := cmd.Flags().GetBool("json") - - var data []byte - if jsonFlag || len(to) == 0 { - data, err = btok.MarshalJSON() - common.ExitOnErr(cmd, "can't JSON encode bearer token: %w", err) - } else { - data = btok.Marshal() - } - - if len(to) == 0 { - prettyPrintJSON(cmd, data) - - return - } - - err = os.WriteFile(to, data, 0644) - common.ExitOnErr(cmd, "can't write signed bearer token to file: %w", err) - - cmd.Printf("signed bearer token was successfully dumped to %s\n", to) -} - -func signSessionToken(cmd *cobra.Command, _ []string) { - path, err := cmd.Flags().GetString("from") - common.ExitOnErr(cmd, "", err) - - stok, err := getSessionToken(path) - if err != nil { - common.ExitOnErr(cmd, "", fmt.Errorf("can't read session token from %s: %w", path, err)) - } - - pk := key.GetOrGenerate(cmd) - - err = stok.Sign(pk) - common.ExitOnErr(cmd, "can't sign token: %w", err) - - data, err := stok.MarshalJSON() - common.ExitOnErr(cmd, "can't encode session token: %w", err) - - to := cmd.Flag("to").Value.String() - if len(to) == 0 { - prettyPrintJSON(cmd, data) - return - } - - err = os.WriteFile(to, data, 0644) - if err != nil { - common.ExitOnErr(cmd, "", fmt.Errorf("can't write signed session token to %s: %w", to, err)) - } - - cmd.Printf("signed session token saved in %s\n", to) -} - -func convertEACLTable(cmd *cobra.Command, _ []string) { - pathFrom := cmd.Flag("from").Value.String() - to := cmd.Flag("to").Value.String() - jsonFlag, _ := cmd.Flags().GetBool("json") - - table, err := parseEACL(pathFrom) - common.ExitOnErr(cmd, "", err) - - var data []byte - if jsonFlag || len(to) == 0 { - data, err = table.MarshalJSON() - common.ExitOnErr(cmd, "can't JSON encode extended ACL table: %w", err) - } else { - data, err = table.Marshal() - common.ExitOnErr(cmd, "can't binary encode extended ACL table: %w", err) - } - - if len(to) == 0 { - prettyPrintJSON(cmd, data) - return - } - err = os.WriteFile(to, data, 0644) - common.ExitOnErr(cmd, "can't write exteded ACL table to file: %w", err) - - cmd.Printf("extended ACL table was successfully dumped to %s\n", to) -} - -func processKeyer(cmd *cobra.Command, args []string) { - var ( - err error - - result = new(keyer.Dashboard) - generate, _ = cmd.Flags().GetBool("generate") - useHex, _ = cmd.Flags().GetBool("hex") - uncompressed, _ = cmd.Flags().GetBool("uncompressed") - multisig, _ = cmd.Flags().GetBool("multisig") - ) - - if multisig { - err = result.ParseMultiSig(args) - } else { - if len(args) > 1 { - common.ExitOnErr(cmd, "", errKeyerSingleArgument) - } - - var argument string - if len(args) > 0 { - argument = args[0] - } - - switch { - case generate: - err = keyerGenerate(argument, result) - case fileExists(argument): - err = keyerParseFile(argument, result) - default: - err = result.ParseString(argument) - } - } - - common.ExitOnErr(cmd, "", err) - - result.PrettyPrint(uncompressed, useHex) -} - -func prettyPrintJSON(cmd *cobra.Command, data []byte) { - buf := new(bytes.Buffer) - if err := json.Indent(buf, data, "", " "); err != nil { - common.PrintVerbose("Can't pretty print json: %w", err) - } - - cmd.Println(buf) -} - -func prettyPrintUnixTime(s string) string { - unixTime, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return "malformed" - } - - timestamp := time.Unix(unixTime, 0) - - return timestamp.String() -} - -func keyerGenerate(filename string, d *keyer.Dashboard) error { - key := make([]byte, keyer.NeoPrivateKeySize) - - _, err := rand.Read(key) - if err != nil { - return fmt.Errorf("can't get random source: %w", err) - } - - err = d.ParseBinary(key) - if err != nil { - return fmt.Errorf("can't parse key: %w", err) - } - - if filename != "" { - return os.WriteFile(filename, key, 0600) - } - - return nil -} - -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - - return !info.IsDir() -} - -func keyerParseFile(filename string, d *keyer.Dashboard) error { - data, err := os.ReadFile(filename) - if err != nil { - return fmt.Errorf("can't open %v file: %w", filename, err) - } - - return d.ParseBinary(data) -} diff --git a/cmd/neofs-cli/modules/util/convert.go b/cmd/neofs-cli/modules/util/convert.go new file mode 100644 index 0000000000..d1229042ac --- /dev/null +++ b/cmd/neofs-cli/modules/util/convert.go @@ -0,0 +1,14 @@ +package util + +import "github.com/spf13/cobra" + +var convertCmd = &cobra.Command{ + Use: "convert", + Short: "Convert representation of NeoFS structures", +} + +func initConvertCmd() { + convertCmd.AddCommand(convertEACLCmd) + + initConvertEACLCmd() +} diff --git a/cmd/neofs-cli/modules/util/convert_eacl.go b/cmd/neofs-cli/modules/util/convert_eacl.go new file mode 100644 index 0000000000..d55405a3f4 --- /dev/null +++ b/cmd/neofs-cli/modules/util/convert_eacl.go @@ -0,0 +1,64 @@ +package util + +import ( + "bytes" + "encoding/json" + "os" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/spf13/cobra" +) + +var convertEACLCmd = &cobra.Command{ + Use: "eacl", + Short: "Convert representation of extended ACL table", + Run: convertEACLTable, +} + +func initConvertEACLCmd() { + flags := convertEACLCmd.Flags() + + flags.String("from", "", "File with JSON or binary encoded extended ACL table") + _ = convertEACLCmd.MarkFlagFilename("from") + _ = convertEACLCmd.MarkFlagRequired("from") + + flags.String("to", "", "File to dump extended ACL table (default: binary encoded)") + flags.Bool("json", false, "Dump extended ACL table in JSON encoding") +} + +func convertEACLTable(cmd *cobra.Command, _ []string) { + pathFrom := cmd.Flag("from").Value.String() + to := cmd.Flag("to").Value.String() + jsonFlag, _ := cmd.Flags().GetBool("json") + + table := common.ReadEACL(cmd, pathFrom) + + var data []byte + var err error + if jsonFlag || len(to) == 0 { + data, err = table.MarshalJSON() + common.ExitOnErr(cmd, "can't JSON encode extended ACL table: %w", err) + } else { + data, err = table.Marshal() + common.ExitOnErr(cmd, "can't binary encode extended ACL table: %w", err) + } + + if len(to) == 0 { + prettyPrintJSON(cmd, data) + return + } + + err = os.WriteFile(to, data, 0644) + common.ExitOnErr(cmd, "can't write exteded ACL table to file: %w", err) + + cmd.Printf("extended ACL table was successfully dumped to %s\n", to) +} + +func prettyPrintJSON(cmd *cobra.Command, data []byte) { + buf := new(bytes.Buffer) + if err := json.Indent(buf, data, "", " "); err != nil { + common.PrintVerbose("Can't pretty print json: %w", err) + } + + cmd.Println(buf) +} diff --git a/cmd/neofs-cli/modules/util/keyer.go b/cmd/neofs-cli/modules/util/keyer.go new file mode 100644 index 0000000000..1ef19bc913 --- /dev/null +++ b/cmd/neofs-cli/modules/util/keyer.go @@ -0,0 +1,99 @@ +package util + +import ( + "crypto/rand" + "errors" + "fmt" + "os" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/pkg/util/keyer" + "github.com/spf13/cobra" +) + +var keyerCmd = &cobra.Command{ + Use: "keyer", + Short: "Generate or print information about keys", + Run: processKeyer, +} + +var errKeyerSingleArgument = errors.New("pass only one argument at a time") + +func initKeyerCmd() { + keyerCmd.Flags().BoolP("generate", "g", false, "generate new private key") + keyerCmd.Flags().Bool("hex", false, "print all values in hex encoding") + keyerCmd.Flags().BoolP("uncompressed", "u", false, "use uncompressed public key format") + keyerCmd.Flags().BoolP("multisig", "m", false, "calculate multisig address from public keys") +} + +func processKeyer(cmd *cobra.Command, args []string) { + var ( + err error + + result = new(keyer.Dashboard) + generate, _ = cmd.Flags().GetBool("generate") + useHex, _ = cmd.Flags().GetBool("hex") + uncompressed, _ = cmd.Flags().GetBool("uncompressed") + multisig, _ = cmd.Flags().GetBool("multisig") + ) + + if multisig { + err = result.ParseMultiSig(args) + } else { + if len(args) > 1 { + common.ExitOnErr(cmd, "", errKeyerSingleArgument) + } + + var argument string + if len(args) > 0 { + argument = args[0] + } + + switch { + case generate: + err = keyerGenerate(argument, result) + case fileExists(argument): + err = keyerParseFile(argument, result) + default: + err = result.ParseString(argument) + } + } + + common.ExitOnErr(cmd, "", err) + + result.PrettyPrint(uncompressed, useHex) +} + +func keyerGenerate(filename string, d *keyer.Dashboard) error { + key := make([]byte, keyer.NeoPrivateKeySize) + + _, err := rand.Read(key) + if err != nil { + return fmt.Errorf("can't get random source: %w", err) + } + + err = d.ParseBinary(key) + if err != nil { + return fmt.Errorf("can't parse key: %w", err) + } + + if filename != "" { + return os.WriteFile(filename, key, 0600) + } + + return nil +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + return !os.IsNotExist(err) && !info.IsDir() +} + +func keyerParseFile(filename string, d *keyer.Dashboard) error { + data, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("can't open %v file: %w", filename, err) + } + + return d.ParseBinary(data) +} diff --git a/cmd/neofs-cli/modules/util/locode.go b/cmd/neofs-cli/modules/util/locode.go new file mode 100644 index 0000000000..8ebf62b9ae --- /dev/null +++ b/cmd/neofs-cli/modules/util/locode.go @@ -0,0 +1,18 @@ +package util + +import ( + "github.com/spf13/cobra" +) + +// locode section +var locodeCmd = &cobra.Command{ + Use: "locode", + Short: "Working with NeoFS UN/LOCODE database", +} + +func initLocodeCmd() { + locodeCmd.AddCommand(locodeGenerateCmd, locodeInfoCmd) + + initUtilLocodeInfoCmd() + initUtilLocodeGenerateCmd() +} diff --git a/cmd/neofs-cli/modules/util/locode_generate.go b/cmd/neofs-cli/modules/util/locode_generate.go new file mode 100644 index 0000000000..670f6362f4 --- /dev/null +++ b/cmd/neofs-cli/modules/util/locode_generate.go @@ -0,0 +1,96 @@ +package util + +import ( + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + locodedb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db" + airportsdb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/airports" + locodebolt "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/boltdb" + continentsdb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/continents/geojson" + csvlocode "github.com/nspcc-dev/neofs-node/pkg/util/locode/table/csv" + "github.com/spf13/cobra" +) + +type namesDB struct { + *airportsdb.DB + *csvlocode.Table +} + +const ( + locodeGenerateInputFlag = "in" + locodeGenerateSubDivFlag = "subdiv" + locodeGenerateAirportsFlag = "airports" + locodeGenerateCountriesFlag = "countries" + locodeGenerateContinentsFlag = "continents" + locodeGenerateOutputFlag = "out" +) + +var ( + locodeGenerateInPaths []string + locodeGenerateSubDivPath string + locodeGenerateAirportsPath string + locodeGenerateCountriesPath string + locodeGenerateContinentsPath string + locodeGenerateOutPath string + + locodeGenerateCmd = &cobra.Command{ + Use: "generate", + Short: "generate UN/LOCODE database for NeoFS", + Run: func(cmd *cobra.Command, _ []string) { + locodeDB := csvlocode.New( + csvlocode.Prm{ + Path: locodeGenerateInPaths[0], + SubDivPath: locodeGenerateSubDivPath, + }, + csvlocode.WithExtraPaths(locodeGenerateInPaths[1:]...), + ) + + airportDB := airportsdb.New(airportsdb.Prm{ + AirportsPath: locodeGenerateAirportsPath, + CountriesPath: locodeGenerateCountriesPath, + }) + + continentsDB := continentsdb.New(continentsdb.Prm{ + Path: locodeGenerateContinentsPath, + }) + + targetDB := locodebolt.New(locodebolt.Prm{ + Path: locodeGenerateOutPath, + }) + + err := targetDB.Open() + common.ExitOnErr(cmd, "", err) + + defer targetDB.Close() + + names := &namesDB{ + DB: airportDB, + Table: locodeDB, + } + + err = locodedb.FillDatabase(locodeDB, airportDB, continentsDB, names, targetDB) + common.ExitOnErr(cmd, "", err) + }, + } +) + +func initUtilLocodeGenerateCmd() { + flags := locodeGenerateCmd.Flags() + + flags.StringSliceVar(&locodeGenerateInPaths, locodeGenerateInputFlag, nil, "List of paths to UN/LOCODE tables (csv)") + _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateInputFlag) + + flags.StringVar(&locodeGenerateSubDivPath, locodeGenerateSubDivFlag, "", "Path to UN/LOCODE subdivision database (csv)") + _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateSubDivFlag) + + flags.StringVar(&locodeGenerateAirportsPath, locodeGenerateAirportsFlag, "", "Path to OpenFlights airport database (csv)") + _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateAirportsFlag) + + flags.StringVar(&locodeGenerateCountriesPath, locodeGenerateCountriesFlag, "", "Path to OpenFlights country database (csv)") + _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateCountriesFlag) + + flags.StringVar(&locodeGenerateContinentsPath, locodeGenerateContinentsFlag, "", "Path to continent polygons (GeoJSON)") + _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateContinentsFlag) + + flags.StringVar(&locodeGenerateOutPath, locodeGenerateOutputFlag, "", "Target path for generated database") + _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateOutputFlag) +} diff --git a/cmd/neofs-cli/modules/util/locode_info.go b/cmd/neofs-cli/modules/util/locode_info.go new file mode 100644 index 0000000000..ef2e2f3cf2 --- /dev/null +++ b/cmd/neofs-cli/modules/util/locode_info.go @@ -0,0 +1,56 @@ +package util + +import ( + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + locodedb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db" + locodebolt "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/boltdb" + "github.com/spf13/cobra" +) + +const ( + locodeInfoDBFlag = "db" + locodeInfoCodeFlag = "locode" +) + +var ( + locodeInfoDBPath string + locodeInfoCode string + + locodeInfoCmd = &cobra.Command{ + Use: "info", + Short: "print information about UN/LOCODE from NeoFS database", + Run: func(cmd *cobra.Command, _ []string) { + targetDB := locodebolt.New(locodebolt.Prm{ + Path: locodeInfoDBPath, + }, locodebolt.ReadOnly()) + + err := targetDB.Open() + common.ExitOnErr(cmd, "", err) + + defer targetDB.Close() + + record, err := locodedb.LocodeRecord(targetDB, locodeInfoCode) + common.ExitOnErr(cmd, "", err) + + cmd.Printf("Country: %s\n", record.CountryName()) + cmd.Printf("Location: %s\n", record.LocationName()) + cmd.Printf("Continent: %s\n", record.Continent()) + if subDivCode := record.SubDivCode(); subDivCode != "" { + cmd.Printf("Subdivision: [%s] %s\n", subDivCode, record.SubDivName()) + } + + geoPoint := record.GeoPoint() + cmd.Printf("Coordinates: %0.2f, %0.2f\n", geoPoint.Latitude(), geoPoint.Longitude()) + }, + } +) + +func initUtilLocodeInfoCmd() { + flags := locodeInfoCmd.Flags() + + flags.StringVar(&locodeInfoDBPath, locodeInfoDBFlag, "", "Path to NeoFS UN/LOCODE database") + _ = locodeInfoCmd.MarkFlagRequired(locodeInfoDBFlag) + + flags.StringVar(&locodeInfoCode, locodeInfoCodeFlag, "", "UN/LOCODE") + _ = locodeInfoCmd.MarkFlagRequired(locodeInfoCodeFlag) +} diff --git a/cmd/neofs-cli/modules/util/root.go b/cmd/neofs-cli/modules/util/root.go new file mode 100644 index 0000000000..be29631430 --- /dev/null +++ b/cmd/neofs-cli/modules/util/root.go @@ -0,0 +1,33 @@ +package util + +import ( + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var Cmd = &cobra.Command{ + Use: "util", + Short: "Utility operations", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + + _ = viper.BindPFlag(commonflags.GenerateKey, flags.Lookup(commonflags.GenerateKey)) + _ = viper.BindPFlag(commonflags.WalletPath, flags.Lookup(commonflags.WalletPath)) + _ = viper.BindPFlag(commonflags.Account, flags.Lookup(commonflags.Account)) + }, +} + +func init() { + Cmd.AddCommand( + signCmd, + convertCmd, + keyerCmd, + locodeCmd, + ) + + initSignCmd() + initConvertCmd() + initKeyerCmd() + initLocodeCmd() +} diff --git a/cmd/neofs-cli/modules/util/sign.go b/cmd/neofs-cli/modules/util/sign.go new file mode 100644 index 0000000000..487d204844 --- /dev/null +++ b/cmd/neofs-cli/modules/util/sign.go @@ -0,0 +1,22 @@ +package util + +import ( + "github.com/spf13/cobra" +) + +const ( + signFromFlag = "from" + signToFlag = "to" +) + +var signCmd = &cobra.Command{ + Use: "sign", + Short: "Sign NeoFS structure", +} + +func initSignCmd() { + signCmd.AddCommand(signBearerCmd, signSessionCmd) + + initSignBearerCmd() + initSignSessionCmd() +} diff --git a/cmd/neofs-cli/modules/util/sign_bearer.go b/cmd/neofs-cli/modules/util/sign_bearer.go new file mode 100644 index 0000000000..877077f0ed --- /dev/null +++ b/cmd/neofs-cli/modules/util/sign_bearer.go @@ -0,0 +1,63 @@ +package util + +import ( + "os" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" + "github.com/spf13/cobra" +) + +const ( + signBearerJSONFlag = "json" +) + +var signBearerCmd = &cobra.Command{ + Use: "bearer-token", + Short: "Sign bearer token to use it in requests", + Run: signBearerToken, +} + +func initSignBearerCmd() { + commonflags.InitWithoutRPC(signBearerCmd) + + flags := signBearerCmd.Flags() + + flags.String(signFromFlag, "", "File with JSON or binary encoded bearer token to sign") + _ = signBearerCmd.MarkFlagFilename(signFromFlag) + _ = signBearerCmd.MarkFlagRequired(signFromFlag) + + flags.String(signToFlag, "", "File to dump signed bearer token (default: binary encoded)") + flags.Bool(signBearerJSONFlag, false, "Dump bearer token in JSON encoding") +} + +func signBearerToken(cmd *cobra.Command, _ []string) { + btok := common.ReadBearerToken(cmd, signFromFlag) + pk := key.GetOrGenerate(cmd) + + err := btok.Sign(*pk) + common.ExitOnErr(cmd, "", err) + + to := cmd.Flag(signToFlag).Value.String() + jsonFlag, _ := cmd.Flags().GetBool(signBearerJSONFlag) + + var data []byte + if jsonFlag || len(to) == 0 { + data, err = btok.MarshalJSON() + common.ExitOnErr(cmd, "can't JSON encode bearer token: %w", err) + } else { + data = btok.Marshal() + } + + if len(to) == 0 { + prettyPrintJSON(cmd, data) + + return + } + + err = os.WriteFile(to, data, 0644) + common.ExitOnErr(cmd, "can't write signed bearer token to file: %w", err) + + cmd.Printf("signed bearer token was successfully dumped to %s\n", to) +} diff --git a/cmd/neofs-cli/modules/util/sign_session.go b/cmd/neofs-cli/modules/util/sign_session.go new file mode 100644 index 0000000000..b441d6670a --- /dev/null +++ b/cmd/neofs-cli/modules/util/sign_session.go @@ -0,0 +1,53 @@ +package util + +import ( + "fmt" + "os" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" + "github.com/spf13/cobra" +) + +var signSessionCmd = &cobra.Command{ + Use: "session-token", + Short: "Sign session token to use it in requests", + Run: signSessionToken, +} + +func initSignSessionCmd() { + commonflags.InitWithoutRPC(signSessionCmd) + + flags := signSessionCmd.Flags() + + flags.String(signFromFlag, "", "File with JSON encoded session token to sign") + _ = signSessionCmd.MarkFlagFilename(signFromFlag) + _ = signSessionCmd.MarkFlagRequired(signFromFlag) + + flags.String(signToFlag, "", "File to save signed session token (optional)") +} + +func signSessionToken(cmd *cobra.Command, _ []string) { + stok := common.ReadSessionToken(cmd, signFromFlag) + pk := key.GetOrGenerate(cmd) + + err := stok.Sign(pk) + common.ExitOnErr(cmd, "can't sign token: %w", err) + + data, err := stok.MarshalJSON() + common.ExitOnErr(cmd, "can't encode session token: %w", err) + + to := cmd.Flag(signToFlag).Value.String() + if len(to) == 0 { + prettyPrintJSON(cmd, data) + return + } + + err = os.WriteFile(to, data, 0644) + if err != nil { + common.ExitOnErr(cmd, "", fmt.Errorf("can't write signed session token to %s: %w", to, err)) + } + + cmd.Printf("signed session token saved in %s\n", to) +}