cmd/swarm: make bzzaccount flag optional and add bzzkeyhex flag (#1531)

* cmd/swarm: don't require bzzaccount flag

* docker: remove setup script because bzzaccount is not required anymore

* cmd/swarm: manage account creation/selection when bzzflag is not defined

* cmd/swarm: no bzzaccount flag tests

* cmd/swarm: use random network ports on tests

* cmd/swarm: fix typo

* cmd/swarm: add --bzzkeyhex flag

* cmd/swarm: use different ipc paths for tests

* readme: update example on how to run swarm

* cmd/swarm: rename getAccount -> getOrCreateAccount

* cmd/swarm: remove unneeded comment

* cmd/swarm: use shorter ipc path for test
This commit is contained in:
Rafael Matias
2019-07-04 13:14:10 +02:00
committed by GitHub
parent fb73e6cb9b
commit d5f6ee4620
10 changed files with 266 additions and 94 deletions

View File

@@ -106,7 +106,7 @@ func accessNewPass(ctx *cli.Context) {
accessKey []byte
err error
ref = args[0]
password = getPassPhrase("", 0, makePasswordList(ctx))
password = getPassPhrase("", false, 0, makePasswordList(ctx))
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
accessKey, ae, err = api.DoPassword(ctx, password, salt)

View File

@@ -17,6 +17,7 @@
package main
import (
"crypto/ecdsa"
"errors"
"fmt"
"io"
@@ -60,6 +61,7 @@ var (
const (
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
SwarmEnvAccount = "SWARM_ACCOUNT"
SwarmEnvBzzKeyHex = "SWARM_BZZ_KEY_HEX"
SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
SwarmEnvPort = "SWARM_PORT"
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
@@ -121,9 +123,9 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
//finally, after the configuration build phase is finished, initialize
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context, nodeconfig *node.Config) error {
//at this point, all vars should be set in the Config
//get the account for the provided swarm account
prvkey := getAccount(config.BzzAccount, ctx, stack)
var prvkey *ecdsa.PrivateKey
config.BzzAccount, prvkey = getOrCreateAccount(ctx, stack)
//set the resolved config path (geth --datadir)
config.Path = expandPath(stack.InstanceDir())
//finally, initialize the configuration

View File

@@ -17,6 +17,7 @@
package main
import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
@@ -28,6 +29,8 @@ import (
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
"github.com/ethersphere/swarm/api"
@@ -48,6 +51,7 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
}
@@ -56,15 +60,161 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
swarm.ExpectExit()
}
func TestConfigFailsNoBzzAccount(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
func TestBzzKeyFlag(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("unable to generate key")
}
hexKey := hex.EncodeToString(crypto.FromECDSA(key))
bzzaccount := crypto.PubkeyToAddress(key.PublicKey).Hex()
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
conf := &node.Config{
DataDir: dir,
IPCPath: "testbzzkeyflag.ipc",
NoUSB: true,
}
swarm := runSwarm(t, flags...)
swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
swarm.ExpectExit()
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
fmt.Sprintf("--%s", SwarmBzzKeyHexFlag.Name), hexKey,
}
node.Cmd = runSwarm(t, flags...)
defer func() {
node.Shutdown()
}()
node.Cmd.InputLine(testPassphrase)
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
if info.BzzAccount != bzzaccount {
t.Fatalf("Expected account to be %s, got %s", bzzaccount, info.BzzAccount)
}
}
func TestEmptyBzzAccountFlagMultipleAccounts(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create two accounts on disk
getTestAccount(t, dir)
getTestAccount(t, dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
}
node.Cmd = runSwarm(t, flags...)
node.Cmd.ExpectRegexp(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
}
func TestEmptyBzzAccountFlagSingleAccount(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
conf, account := getTestAccount(t, dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
}
node.Cmd = runSwarm(t, flags...)
defer func() {
node.Shutdown()
}()
node.Cmd.InputLine(testPassphrase)
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
if info.BzzAccount != account.Address.Hex() {
t.Fatalf("Expected account to be %s, got %s", account.Address.Hex(), info.BzzAccount)
}
}
func TestEmptyBzzAccountFlagNoAccountWrongPassword(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
}
node.Cmd = runSwarm(t, flags...)
// Set password
node.Cmd.InputLine("goodpassword")
// Confirm password
node.Cmd.InputLine("wrongpassword")
node.Cmd.ExpectRegexp("Passphrases do not match")
}
func TestConfigCmdLineOverrides(t *testing.T) {
@@ -86,6 +236,7 @@ func TestConfigCmdLineOverrides(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),

View File

@@ -101,7 +101,7 @@ func download(ctx *cli.Context) {
return nil
}
if passwords := makePasswordList(ctx); passwords != nil {
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), false, 0, passwords)
err = dl(password)
} else {
err = dl("")

View File

@@ -30,6 +30,11 @@ var (
Usage: "Swarm account key file",
EnvVar: SwarmEnvAccount,
}
SwarmBzzKeyHexFlag = cli.StringFlag{
Name: "bzzkeyhex",
Usage: "BzzAccount key in hex (for testing)",
EnvVar: SwarmEnvBzzKeyHex,
}
SwarmListenAddrFlag = cli.StringFlag{
Name: "httpaddr",
Usage: "Swarm HTTP API listening interface",

View File

@@ -184,6 +184,7 @@ func init() {
SwarmListenAddrFlag,
SwarmPortFlag,
SwarmAccountFlag,
SwarmBzzKeyHexFlag,
SwarmNetworkIdFlag,
ChequebookAddrFlag,
// upload flags
@@ -351,21 +352,62 @@ func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
}
}
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
//an account is mandatory
if bzzaccount == "" {
utils.Fatalf(SwarmErrNoBZZAccount)
// getOrCreateAccount returns the address and associated private key for a bzzaccount
// If no account exists, it will create an account for you.
func getOrCreateAccount(ctx *cli.Context, stack *node.Node) (string, *ecdsa.PrivateKey) {
var bzzaddr string
// Check if a key was provided
if hexkey := ctx.GlobalString(SwarmBzzKeyHexFlag.Name); hexkey != "" {
key, err := crypto.HexToECDSA(hexkey)
if err != nil {
utils.Fatalf("failed using %s: %v", SwarmBzzKeyHexFlag.Name, err)
}
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
log.Info(fmt.Sprintf("Swarm account key loaded from %s", SwarmBzzKeyHexFlag.Name), "address", bzzaddr)
return bzzaddr, key
}
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
return key
}
// Otherwise try getting it from the keystore.
am := stack.AccountManager()
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
// Check if an address was provided
if bzzaddr = ctx.GlobalString(SwarmAccountFlag.Name); bzzaddr != "" {
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(bzzaddr); err == nil {
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
log.Info("Swarm account key loaded", "address", bzzaddr)
return bzzaddr, key
}
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
}
// No address or key were provided
accounts := ks.Accounts()
switch l := len(accounts); l {
case 0:
// Create an account
log.Info("You don't have an account yet. Creating one...")
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
account, err := ks.NewAccount(password)
if err != nil {
utils.Fatalf("failed creating an account: %v", err)
}
bzzaddr = account.Address.Hex()
case 1:
// Use existing account
bzzaddr = accounts[0].Address.Hex()
default:
// Inform user about multiple accounts
log.Info(fmt.Sprintf("Multiple (%d) accounts were found in your keystore.", l))
for _, a := range accounts {
log.Info(fmt.Sprintf("Account: %s", a.Address.Hex()))
}
utils.Fatalf(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
}
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
}
// getPrivKey returns the private key of the specified bzzaccount
@@ -387,7 +429,9 @@ func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
}
defer stack.Close()
return getAccount(bzzconfig.BzzAccount, ctx, stack)
var privkey *ecdsa.PrivateKey
bzzconfig.BzzAccount, privkey = getOrCreateAccount(ctx, stack)
return privkey
}
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
@@ -412,7 +456,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
utils.Fatalf("Can't load swarm account key: %v", err)
}
for i := 0; i < 3; i++ {
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), false, i, passwords)
key, err := keystore.DecryptKey(keyjson, password)
if err == nil {
return key.PrivateKey
@@ -422,18 +466,17 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
return nil
}
// getPassPhrase retrieves the password associated with bzz account, either by fetching
// from a list of pre-loaded passwords, or by requesting it interactively from user.
func getPassPhrase(prompt string, i int, passwords []string) string {
// non-interactive
// getPassPhrase retrieves the password associated with a bzzaccount, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
// If a list of passwords was supplied, retrieve from them
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}
// fallback to interactive mode
// Otherwise prompt the user for the password
if prompt != "" {
fmt.Println(prompt)
}
@@ -441,6 +484,15 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return password
}