accountcmd_plugin.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package main
  2. import (
  3. "encoding/hex"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "github.com/ethereum/go-ethereum/accounts"
  8. "github.com/ethereum/go-ethereum/accounts/pluggable"
  9. "github.com/ethereum/go-ethereum/cmd/utils"
  10. "github.com/ethereum/go-ethereum/crypto"
  11. "github.com/ethereum/go-ethereum/log"
  12. "github.com/ethereum/go-ethereum/node"
  13. "github.com/ethereum/go-ethereum/plugin"
  14. "gopkg.in/urfave/cli.v1"
  15. )
  16. var (
  17. quorumAccountPluginCommands = cli.Command{
  18. Name: "plugin",
  19. Usage: "Manage 'account' plugin accounts",
  20. Description: `
  21. geth account plugin
  22. Quorum supports alternate account management methods through the use of 'account' plugins.
  23. See docs.goquorum.com for more info.
  24. `,
  25. Subcommands: []cli.Command{
  26. {
  27. Name: "list",
  28. Usage: "Print summary of existing 'account' plugin accounts",
  29. Action: utils.MigrateFlags(listPluginAccountsCLIAction),
  30. Flags: []cli.Flag{
  31. utils.PluginSettingsFlag, // flag is used implicitly by makeConfigNode()
  32. utils.PluginLocalVerifyFlag,
  33. utils.PluginPublicKeyFlag,
  34. utils.PluginSkipVerifyFlag,
  35. },
  36. Description: `
  37. geth account plugin list
  38. Print a short summary of all accounts for the given plugin settings`,
  39. },
  40. {
  41. Name: "new",
  42. Usage: "Create a new account using an 'account' plugin",
  43. Action: utils.MigrateFlags(createPluginAccountCLIAction),
  44. Flags: []cli.Flag{
  45. utils.PluginSettingsFlag,
  46. utils.PluginLocalVerifyFlag,
  47. utils.PluginPublicKeyFlag,
  48. utils.PluginSkipVerifyFlag,
  49. utils.AccountPluginNewAccountConfigFlag,
  50. },
  51. Description: fmt.Sprintf(`
  52. geth account plugin new
  53. Creates a new account using an 'account' plugin and prints the address.
  54. --%v and --%v flags are required.
  55. Each 'account' plugin will have different requirements for the value of --%v.
  56. For more info see the documentation for the particular 'account' plugin being used.
  57. `, utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name),
  58. },
  59. {
  60. Name: "import",
  61. Usage: "Import a private key into a new account using an 'account' plugin",
  62. Action: utils.MigrateFlags(importPluginAccountCLIAction),
  63. Flags: []cli.Flag{
  64. utils.PluginSettingsFlag,
  65. utils.PluginLocalVerifyFlag,
  66. utils.PluginPublicKeyFlag,
  67. utils.PluginSkipVerifyFlag,
  68. utils.AccountPluginNewAccountConfigFlag,
  69. },
  70. ArgsUsage: "<keyFile>",
  71. Description: `
  72. geth account plugin import <keyfile>
  73. Imports an unencrypted private key from <keyfile> and creates a new account using an 'account' plugin.
  74. Prints the address.
  75. The keyfile must contain an unencrypted private key in hexadecimal format.
  76. --%v and --%v flags are required.
  77. Note:
  78. Before using this import mechanism to transfer accounts that are already 'account' plugin-managed between nodes, consult
  79. the documentation for the particular 'account' plugin being used as it may support alternate methods for transferring.
  80. `,
  81. },
  82. },
  83. }
  84. // supportedPlugins is the list of supported plugins for the account subcommand
  85. supportedPlugins = []plugin.PluginInterfaceName{plugin.AccountPluginInterfaceName}
  86. invalidPluginFlagsErr = fmt.Errorf("--%v and --%v flags must be set", utils.PluginSettingsFlag.Name, utils.AccountPluginNewAccountConfigFlag.Name)
  87. // makeConfigNodeDelegate is a wrapper for the makeConfigNode function.
  88. // It can be replaced with a stub for testing.
  89. makeConfigNodeDelegate configNodeMaker = standardConfigNodeMaker{}
  90. )
  91. func listPluginAccountsCLIAction(ctx *cli.Context) error {
  92. accts, err := listPluginAccounts(ctx)
  93. if err != nil {
  94. utils.Fatalf("%v", err)
  95. }
  96. var index int
  97. for _, acct := range accts {
  98. fmt.Printf("Account #%d: {%x} %s\n", index, acct.Address, &acct.URL)
  99. index++
  100. }
  101. return nil
  102. }
  103. func listPluginAccounts(ctx *cli.Context) ([]accounts.Account, error) {
  104. if !ctx.IsSet(utils.PluginSettingsFlag.Name) {
  105. return []accounts.Account{}, fmt.Errorf("--%v required", utils.PluginSettingsFlag.Name)
  106. }
  107. p, err := setupAccountPluginForCLI(ctx)
  108. if err != nil {
  109. return []accounts.Account{}, err
  110. }
  111. defer func() {
  112. if err := p.teardown(); err != nil {
  113. log.Error("error tearing down account plugin", "err", err)
  114. }
  115. }()
  116. return p.accounts(), nil
  117. }
  118. func createPluginAccountCLIAction(ctx *cli.Context) error {
  119. account, err := createPluginAccount(ctx)
  120. if err != nil {
  121. utils.Fatalf("unable to create plugin-backed account: %v", err)
  122. }
  123. writePluginAccountToStdOut(account)
  124. return nil
  125. }
  126. func createPluginAccount(ctx *cli.Context) (accounts.Account, error) {
  127. if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) {
  128. return accounts.Account{}, invalidPluginFlagsErr
  129. }
  130. newAcctCfg, err := getNewAccountConfigFromCLI(ctx)
  131. if err != nil {
  132. return accounts.Account{}, err
  133. }
  134. p, err := setupAccountPluginForCLI(ctx)
  135. if err != nil {
  136. return accounts.Account{}, err
  137. }
  138. defer func() {
  139. if err := p.teardown(); err != nil {
  140. log.Error("error tearing down account plugin", "err", err)
  141. }
  142. }()
  143. return p.NewAccount(newAcctCfg)
  144. }
  145. func importPluginAccountCLIAction(ctx *cli.Context) error {
  146. account, err := importPluginAccount(ctx)
  147. if err != nil {
  148. utils.Fatalf("unable to import key and create plugin-backed account: %v", err)
  149. }
  150. writePluginAccountToStdOut(account)
  151. return nil
  152. }
  153. func importPluginAccount(ctx *cli.Context) (accounts.Account, error) {
  154. keyfile := ctx.Args().First()
  155. if len(keyfile) == 0 {
  156. return accounts.Account{}, errors.New("keyfile must be given as argument")
  157. }
  158. key, err := crypto.LoadECDSA(keyfile)
  159. if err != nil {
  160. return accounts.Account{}, fmt.Errorf("Failed to load the private key: %v", err)
  161. }
  162. keyBytes := crypto.FromECDSA(key)
  163. keyHex := hex.EncodeToString(keyBytes)
  164. if !ctx.IsSet(utils.PluginSettingsFlag.Name) || !ctx.IsSet(utils.AccountPluginNewAccountConfigFlag.Name) {
  165. return accounts.Account{}, invalidPluginFlagsErr
  166. }
  167. newAcctCfg, err := getNewAccountConfigFromCLI(ctx)
  168. if err != nil {
  169. return accounts.Account{}, err
  170. }
  171. p, err := setupAccountPluginForCLI(ctx)
  172. if err != nil {
  173. return accounts.Account{}, err
  174. }
  175. defer func() {
  176. if err := p.teardown(); err != nil {
  177. log.Error("error tearing down account plugin", "err", err)
  178. }
  179. }()
  180. return p.ImportRawKey(keyHex, newAcctCfg)
  181. }
  182. func getNewAccountConfigFromCLI(ctx *cli.Context) (map[string]interface{}, error) {
  183. data := ctx.String(utils.AccountPluginNewAccountConfigFlag.Name)
  184. conf, err := plugin.ReadMultiFormatConfig(data)
  185. if err != nil {
  186. return nil, fmt.Errorf("invalid account creation config provided: %v", err)
  187. }
  188. // plugin backend expects config to be json map
  189. confMap := new(map[string]interface{})
  190. if err := json.Unmarshal(conf, confMap); err != nil {
  191. return nil, fmt.Errorf("invalid account creation config provided: %v", err)
  192. }
  193. return *confMap, nil
  194. }
  195. type accountPlugin struct {
  196. pluggable.AccountCreator
  197. am *accounts.Manager
  198. pm *plugin.PluginManager
  199. }
  200. func (c *accountPlugin) teardown() error {
  201. return c.pm.Stop()
  202. }
  203. func (c *accountPlugin) accounts() []accounts.Account {
  204. b := c.am.Backends(pluggable.BackendType)
  205. if b == nil {
  206. return []accounts.Account{}
  207. }
  208. var accts []accounts.Account
  209. for _, wallet := range b[0].Wallets() {
  210. accts = append(accts, wallet.Accounts()...)
  211. }
  212. return accts
  213. }
  214. // startPluginManagerForAccountCLI is a helper func for use with the account plugin CLI.
  215. // It creates and starts a new PluginManager with the provided CLI flags.
  216. // The caller should call teardown on the returned accountPlugin to stop the plugin after use.
  217. // The returned accountPlugin provides several methods necessary for the account plugin CLI, abstracting the underlying plugin/account types.
  218. //
  219. // This func should not be used for anything other than the account CLI.
  220. // The account plugin, if present, is registered with the existing pluggable.Backend in the stack's AccountManager.
  221. // This allows the AccountManager to use the account plugin even though the PluginManager is not registered with the stack.
  222. // Instead of registering a plugin manager with the stack this is manually creating a plugin manager.
  223. // This means that the plugin manager can be started without having to start the whole stack (P2P client, IPC interface, ...).
  224. // The purpose of this is to help prevent issues/conflicts if an existing node is already running on this host.
  225. //
  226. func setupAccountPluginForCLI(ctx *cli.Context) (*accountPlugin, error) {
  227. stack, cfg := makeConfigNodeDelegate.makeConfigNode(ctx)
  228. if cfg.Node.Plugins == nil {
  229. return nil, errors.New("no plugin config provided")
  230. }
  231. if err := cfg.Node.Plugins.CheckSettingsAreSupported(supportedPlugins); err != nil {
  232. return nil, err
  233. }
  234. if err := cfg.Node.ResolvePluginBaseDir(); err != nil {
  235. return nil, fmt.Errorf("unable to resolve plugin base dir due to %s", err)
  236. }
  237. pm, err := plugin.NewPluginManager(
  238. cfg.Node.UserIdent,
  239. cfg.Node.Plugins,
  240. ctx.Bool(utils.PluginSkipVerifyFlag.Name),
  241. ctx.Bool(utils.PluginLocalVerifyFlag.Name),
  242. ctx.String(utils.PluginPublicKeyFlag.Name),
  243. )
  244. if err != nil {
  245. return nil, fmt.Errorf("unable to create plugin manager: %v", err)
  246. }
  247. if err := pm.Start(); err != nil {
  248. return nil, fmt.Errorf("unable to start plugin manager: %v", err)
  249. }
  250. b := stack.AccountManager().Backends(pluggable.BackendType)[0].(*pluggable.Backend)
  251. if err := pm.AddAccountPluginToBackend(b); err != nil {
  252. return nil, fmt.Errorf("unable to load pluggable account backend: %v", err)
  253. }
  254. return &accountPlugin{
  255. AccountCreator: b,
  256. am: stack.AccountManager(),
  257. pm: pm,
  258. }, nil
  259. }
  260. func writePluginAccountToStdOut(account accounts.Account) {
  261. fmt.Printf("\nYour new plugin-backed account was generated\n\n")
  262. fmt.Printf("Public address of the account: %s\n", account.Address.Hex())
  263. fmt.Printf("Account URL: %s\n\n", account.URL.Path)
  264. fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n")
  265. fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n")
  266. fmt.Printf("- Consider BACKING UP your account! The specifics of backing up will depend on the plugin backend being used.\n")
  267. fmt.Printf("- The plugin backend may require you to REMEMBER part/all of the new account config to retrieve the key in the future!\n See the plugin specific documentation for more info.\n\n")
  268. fmt.Printf("- See the documentation for the plugin being used for more info.\n\n")
  269. }
  270. type configNodeMaker interface {
  271. makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig)
  272. }
  273. // standardConfigNodeMaker is a wrapper around the makeConfigNode function to enable mocking in testing
  274. type standardConfigNodeMaker struct{}
  275. func (f standardConfigNodeMaker) makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
  276. return makeConfigNode(ctx)
  277. }