feat: implement ipfs rotate command for rotating the ID keys for the node
Petar Maymounkov committed
Jun 30, 2020 at 16:20 UTC
2c255eab2420818b0d1050f0797ae3265758ce50
4 files changed
+209
-1
cmd/ipfs/ipfs.go
+1
@@ -24,6 +24,7 @@ var commandsClientCmd = commands.CommandsCmd(Root)
24
var localCommands = map[string]*cmds.Command{
25
"daemon": daemonCmd,
26
"init": initCmd,
27
+ "rotate": rotateCmd,
28
"commands": commandsClientCmd,
29
}
30
cmd/ipfs/rotate.go
new
+115
@@ -0,0 +1,115 @@
1
+package main
2
+
3
+import (
4
+ "fmt"
5
+ "io"
6
+ "os"
7
+
8
+ cmds "github.com/ipfs/go-ipfs-cmds"
9
+ config "github.com/ipfs/go-ipfs-config"
10
+ oldcmds "github.com/ipfs/go-ipfs/commands"
11
+ fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
12
+ "github.com/ipfs/interface-go-ipfs-core/options"
13
+)
14
+
15
+const (
16
+ oldKeyOptionName = "oldkey"
17
+)
18
+
19
+var rotateCmd = &cmds.Command{
20
+ Helptext: cmds.HelpText{
21
+ Tagline: "Rotates the ipfs identity.",
22
+ ShortDescription: `
23
+Generates a new ipfs identity and saves it to the ipfs config file.
24
+The daemon must not be running when calling this command.
25
+
26
+ipfs uses a repository in the local file system. By default, the repo is
27
+located at ~/.ipfs. To change the repo location, set the $IPFS_PATH
28
+environment variable:
29
+
30
+ export IPFS_PATH=/path/to/ipfsrepo
31
+`,
32
+ },
33
+ Arguments: []cmds.Argument{},
34
+ Options: []cmds.Option{
35
+ cmds.StringOption(oldKeyOptionName, "o", "Keystore name for the old/rotated-out key."),
36
+ cmds.StringOption(algorithmOptionName, "a", "Cryptographic algorithm to use for key generation.").WithDefault(algorithmDefault),
37
+ cmds.IntOption(bitsOptionName, "b", "Number of bits to use in the generated RSA private key."),
38
+ },
39
+ PreRun: func(req *cmds.Request, env cmds.Environment) error {
40
+ cctx := env.(*oldcmds.Context)
41
+ daemonLocked, err := fsrepo.LockedByOtherProcess(cctx.ConfigRoot)
42
+ if err != nil {
43
+ return err
44
+ }
45
+
46
+ log.Info("checking if daemon is running...")
47
+ if daemonLocked {
48
+ log.Debug("ipfs daemon is running")
49
+ e := "ipfs daemon is running. please stop it to run this command"
50
+ return cmds.ClientError(e)
51
+ }
52
+
53
+ return nil
54
+ },
55
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
56
+ cctx := env.(*oldcmds.Context)
57
+ nBitsForKeypair, nBitsGiven := req.Options[bitsOptionName].(int)
58
+ algorithm, _ := req.Options[algorithmOptionName].(string)
59
+ oldKey, ok := req.Options[oldKeyOptionName].(string)
60
+ if !ok {
61
+ return fmt.Errorf("keystore name for backing up old key must be provided")
62
+ }
63
+ return doRotate(os.Stdout, cctx.ConfigRoot, oldKey, algorithm, nBitsForKeypair, nBitsGiven)
64
+ },
65
+}
66
+
67
+func doRotate(out io.Writer, repoRoot string, oldKey string, algorithm string, nBitsForKeypair int, nBitsGiven bool) error {
68
+ // Open repo
69
+ repo, err := fsrepo.Open(repoRoot)
70
+ if err != nil {
71
+ return fmt.Errorf("opening repo (%v)", err)
72
+ }
73
+ defer repo.Close()
74
+
75
+ // Read config file from repo
76
+ cfg, err := repo.Config()
77
+ if err != nil {
78
+ return fmt.Errorf("reading config from repo (%v)", err)
79
+ }
80
+
81
+ // Generate new identity
82
+ var identity config.Identity
83
+ if nBitsGiven {
84
+ identity, err = config.CreateIdentity(out, []options.KeyGenerateOption{
85
+ options.Key.Size(nBitsForKeypair),
86
+ options.Key.Type(algorithm),
87
+ })
88
+ } else {
89
+ identity, err = config.CreateIdentity(out, []options.KeyGenerateOption{
90
+ options.Key.Type(algorithm),
91
+ })
92
+ }
93
+ if err != nil {
94
+ return fmt.Errorf("creating identity (%v)", err)
95
+ }
96
+
97
+ // Save old identity to keystore
98
+ oldPrivKey, err := cfg.Identity.DecodePrivateKey("")
99
+ if err != nil {
100
+ return fmt.Errorf("decoding old private key (%v)", err)
101
+ }
102
+ keystore := repo.Keystore()
103
+ if err := keystore.Put(oldKey, oldPrivKey); err != nil {
104
+ return fmt.Errorf("saving old key in keystore (%v)", err)
105
+ }
106
+
107
+ // Update identity
108
+ cfg.Identity = identity
109
+
110
+ // Write config file to repo
111
+ if err = repo.SetConfig(cfg); err != nil {
112
+ return fmt.Errorf("saving new key to config (%v)", err)
113
+ }
114
+ return nil
115
+}
test/sharness/lib/test-lib.sh
+1
-1
@@ -193,7 +193,7 @@ test_init_ipfs() {
193
194
test_expect_success "ipfs init succeeds" '
195
export IPFS_PATH="$(pwd)/.ipfs" &&
196
- ipfs init --profile=test -b=2048 > /dev/null
196
+ ipfs init --profile=test -a=rsa -b=2048 > /dev/null
197
'
198
199
test_expect_success "prepare config -- mounting" '
test/sharness/t0027-rotate.sh
new
+92
@@ -0,0 +1,92 @@
1
+#!/usr/bin/env bash
2
+
3
+test_description="Test rotate command"
4
+
5
+. lib/test-lib.sh
6
+
7
+test_rotate() {
8
+ FROM_ALG=$1
9
+ TO_ALG=$2
10
+
11
+ test_expect_success "ipfs init (from $FROM_ALG, to $TO_ALG)" '
12
+ export IPFS_PATH="$(pwd)/.ipfs" &&
13
+ case $FROM_ALG in
14
+ rsa)
15
+ ipfs init --profile=test -a=rsa > /dev/null
16
+ ;;
17
+ ed25519)
18
+ ipfs init --profile=test -a=ed25519 > /dev/null
19
+ ;;
20
+ *)
21
+ ipfs init --profile=test > /dev/null
22
+ ;;
23
+ esac
24
+ '
25
+
26
+ test_expect_success "Save first ID and key" '
27
+ ipfs id -f="<id>" > first_id &&
28
+ ipfs id -f="<pubkey>" > first_key
29
+ '
30
+
31
+ test_launch_ipfs_daemon
32
+
33
+ test_kill_ipfs_daemon
34
+
35
+ test_expect_success "rotating keys" '
36
+ case $TO_ALG in
37
+ rsa)
38
+ ipfs rotate -a=rsa -b=2048 --oldkey=oldkey
39
+ ;;
40
+ ed25519)
41
+ ipfs rotate -a=ed25519 --oldkey=oldkey
42
+ ;;
43
+ *)
44
+ ipfs rotate --oldkey=oldkey
45
+ ;;
46
+ esac
47
+ '
48
+
49
+ test_expect_success "Compare second ID and key to first" '
50
+ ipfs id -f="<id>" > second_id &&
51
+ ipfs id -f="<pubkey>" > second_key &&
52
+ ! test_cmp first_id second_id &&
53
+ ! test_cmp first_key second_key
54
+ '
55
+
56
+ test_expect_success "checking ID" '
57
+ ipfs config Identity.PeerID > expected-id &&
58
+ ipfs id -f "<id>\n" > actual-id &&
59
+ ipfs key list -l | grep self | cut -d " " -f1 > keystore-id &&
60
+ ipfs key list -l | grep oldkey | cut -d " " -f1 | tr -d "\n" > old-keystore-id &&
61
+ test_cmp expected-id actual-id &&
62
+ test_cmp expected-id keystore-id &&
63
+ test_cmp old-keystore-id first_id
64
+ '
65
+
66
+ test_launch_ipfs_daemon
67
+
68
+ test_expect_success "publish name with new and old keys" '
69
+ echo "hello world" > msg &&
70
+ ipfs add msg | cut -d " " -f2 | tr -d "\n" > msg_hash &&
71
+ ipfs name publish --offline --allow-offline --key=self $(cat msg_hash) &&
72
+ ipfs name publish --offline --allow-offline --key=oldkey $(cat msg_hash)
73
+ '
74
+
75
+ test_kill_ipfs_daemon
76
+
77
+ test_expect_success "clean up ipfs dir" '
78
+ rm -rf "$IPFS_PATH"
79
+ '
80
+
81
+}
82
+test_rotate 'rsa' ''
83
+test_rotate 'ed25519' ''
84
+test_rotate '' ''
85
+test_rotate 'rsa' 'rsa'
86
+test_rotate 'ed25519' 'rsa'
87
+test_rotate '' 'rsa'
88
+test_rotate 'rsa' 'ed25519'
89
+test_rotate 'ed25519' 'ed25519'
90
+test_rotate '' 'ed25519'
91
+
92
+test_done