Skip to content

Commit e3c496f

Browse files
feat(cli): change network commands (#89)
Signed-off-by: Tagscherer Ádám <[email protected]>
1 parent 17305db commit e3c496f

File tree

5 files changed

+116
-74
lines changed

5 files changed

+116
-74
lines changed

Diff for: cli/cmd/network/info/info.go

-5
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,12 @@ import (
1010
"os"
1111

1212
"github.com/agntcy/dir/cli/presenter"
13-
"github.com/agntcy/dir/utils/logging"
1413
"github.com/libp2p/go-libp2p/core/crypto"
1514
"github.com/libp2p/go-libp2p/core/peer"
1615
"github.com/spf13/cobra"
1716
"golang.org/x/crypto/ssh"
1817
)
1918

20-
var logger = logging.Logger("cli/network/info")
21-
2219
var Command = &cobra.Command{
2320
Use: "info",
2421
Short: "Generates the peer id from a private key, enabling connection to the DHT network",
@@ -47,8 +44,6 @@ Usage examples:
4744
}
4845

4946
func runCommand(cmd *cobra.Command, path string) error {
50-
logger.Info("Generating peer ID from key on the filesystem path", "path", path)
51-
5247
// Read the SSH key file
5348
keyBytes, err := os.ReadFile(path)
5449
if err != nil {

Diff for: cli/cmd/network/init/init.go

+61-13
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
package init
55

66
import (
7+
"crypto/ed25519"
78
"crypto/rand"
9+
"crypto/x509"
10+
"encoding/pem"
811
"fmt"
12+
"os"
13+
"path/filepath"
914

1015
"github.com/agntcy/dir/cli/presenter"
11-
"github.com/agntcy/dir/utils/logging"
1216
"github.com/libp2p/go-libp2p/core/crypto"
1317
"github.com/libp2p/go-libp2p/core/peer"
1418
"github.com/spf13/cobra"
1519
)
1620

17-
var logger = logging.Logger("cli/network/init")
18-
1921
var Command = &cobra.Command{
2022
Use: "init",
2123
Short: "Generates the peer id from a newly generated private key, enabling connection to the DHT network",
@@ -25,30 +27,54 @@ a peer id will be generated that is needed for the host to connect to the networ
2527
2628
Usage examples:
2729
28-
1. Generate peer id from a newly generated private key:
30+
1. Generate peer id from a newly generated private key and save the key to the default location (~/.agntcy/dir/generated.key):
2931
3032
dirctl network init
3133
34+
2. Generate peer id from a newly generated private key and save the key to a file:
35+
36+
dirctl network init --output /path/to/private/key.pem
37+
3238
`,
3339
RunE: func(cmd *cobra.Command, _ []string) error {
3440
return runCommand(cmd)
3541
},
3642
}
3743

3844
func runCommand(cmd *cobra.Command) error {
39-
logger.Info("Generating peer ID from a newly generated private key")
45+
publicKey, privateKey, err := GenerateED25519OpenSSLKey()
46+
if err != nil {
47+
return fmt.Errorf("failed to generate ED25519 key pair: %w", err)
48+
}
49+
50+
var filePath string
51+
if opts.Output != "" {
52+
filePath = opts.Output
53+
} else {
54+
homeDir, err := os.UserHomeDir()
55+
if err != nil {
56+
return fmt.Errorf("error getting home directory: %w", err)
57+
}
58+
59+
filePath = filepath.Join(homeDir, ".agntcy/dir/generated.key")
60+
}
61+
62+
err = os.MkdirAll(filepath.Dir(filePath), 0o0755) //nolint:mnd
63+
if err != nil {
64+
return fmt.Errorf("failed to create directory: %w", err)
65+
}
66+
67+
err = os.WriteFile(filePath, privateKey, 0o600) //nolint:mnd
68+
if err != nil {
69+
return fmt.Errorf("failed to write private key to file: %w", err)
70+
}
4071

41-
generatedKey, _, err := crypto.GenerateKeyPairWithReader(
42-
crypto.Ed25519, // Select your key type. Ed25519 are nice short
43-
-1, // Select key length when possible (i.e. RSA).
44-
rand.Reader, // Always generate a random ID
45-
)
72+
pubKey, err := crypto.UnmarshalEd25519PublicKey(publicKey)
4673
if err != nil {
47-
return fmt.Errorf("failed to create identity key: %w", err)
74+
return fmt.Errorf("failed to unmarshal public key: %w", err)
4875
}
4976

50-
// Generate Peer ID from the public key
51-
ID, err := peer.IDFromPublicKey(generatedKey.GetPublic())
77+
ID, err := peer.IDFromPublicKey(pubKey)
5278
if err != nil {
5379
return fmt.Errorf("failed to generate peer ID from public key: %w", err)
5480
}
@@ -57,3 +83,25 @@ func runCommand(cmd *cobra.Command) error {
5783

5884
return nil
5985
}
86+
87+
func GenerateED25519OpenSSLKey() (ed25519.PublicKey, []byte, error) {
88+
// Generate ED25519 key pair
89+
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
90+
if err != nil {
91+
return nil, nil, fmt.Errorf("failed to generate ED25519 key pair: %w", err)
92+
}
93+
94+
// Marshal the private key to PKCS#8 format
95+
privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
96+
if err != nil {
97+
return nil, nil, fmt.Errorf("failed to marshal private key: %w", err)
98+
}
99+
100+
// Create PEM block
101+
pemBlock := &pem.Block{
102+
Type: "PRIVATE KEY",
103+
Bytes: privBytes,
104+
}
105+
106+
return publicKey, pem.EncodeToMemory(pemBlock), nil
107+
}

Diff for: cli/cmd/network/init/options.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright AGNTCY Contributors (https://github.com/agntcy)
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package init
5+
6+
var opts = &options{}
7+
8+
type options struct {
9+
Output string
10+
}
11+
12+
func init() {
13+
flags := Command.Flags()
14+
flags.StringVarP(&opts.Output, "output", "o", "", "Path to the output file, where the generated private key will be stored.")
15+
}

Diff for: cli/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ require (
1313
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
1414
github.com/agntcy/dir/api v0.2.0
1515
github.com/agntcy/dir/client v0.2.0
16-
github.com/agntcy/dir/utils v0.2.0
1716
github.com/anchore/syft v1.19.0
1817
github.com/libp2p/go-libp2p v0.41.1
1918
github.com/mitchellh/mapstructure v1.5.0
@@ -46,6 +45,7 @@ require (
4645
github.com/acobaugh/osrelease v0.1.0 // indirect
4746
github.com/adrg/xdg v0.5.3 // indirect
4847
github.com/agext/levenshtein v1.2.1 // indirect
48+
github.com/agntcy/dir/utils v0.2.0 // indirect
4949
github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 // indirect
5050
github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 // indirect
5151
github.com/anchore/fangs v0.0.0-20250326231402-da263204d38e // indirect

Diff for: e2e/dirctl_test.go

+39-55
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@ package e2e
55

66
import (
77
"bytes"
8-
"crypto/ed25519"
9-
"crypto/rand"
10-
"crypto/x509"
118
_ "embed"
12-
"encoding/pem"
13-
"fmt"
149
"os"
1510
"path/filepath"
1611
"strings"
1712

1813
clicmd "github.com/agntcy/dir/cli/cmd"
14+
initcmd "github.com/agntcy/dir/cli/cmd/network/init"
1915
"github.com/onsi/ginkgo/v2"
2016
"github.com/onsi/gomega"
2117
"github.com/opencontainers/go-digest"
@@ -185,29 +181,29 @@ var _ = ginkgo.Describe("dirctl end-to-end tests", func() {
185181

186182
ginkgo.Context("network commands", func() {
187183
var (
188-
tempKeyDir string
184+
tempDir string
189185
tempKeyPath string
190186
)
191187

192188
ginkgo.BeforeEach(func() {
193189
// Create temporary directory for test keys
194190
var err error
195-
tempKeyDir, err = os.MkdirTemp("", "network-test")
191+
tempDir, err = os.MkdirTemp("", "network-test")
196192
gomega.Expect(err).NotTo(gomega.HaveOccurred())
197193

198194
// Generate OpenSSL-style ED25519 key
199-
privateKey, err := generateED25519OpenSSLKey()
195+
_, privateKey, err := initcmd.GenerateED25519OpenSSLKey()
200196
gomega.Expect(err).NotTo(gomega.HaveOccurred())
201197

202198
// Write the private key to a temporary file
203-
tempKeyPath = filepath.Join(tempKeyDir, "test_key")
199+
tempKeyPath = filepath.Join(tempDir, "test_key")
204200
err = os.WriteFile(tempKeyPath, privateKey, 0o0600)
205201
gomega.Expect(err).NotTo(gomega.HaveOccurred())
206202
})
207203

208204
ginkgo.AfterEach(func() {
209205
// Cleanup temporary directory
210-
err := os.RemoveAll(tempKeyDir)
206+
err := os.RemoveAll(tempDir)
211207
gomega.Expect(err).NotTo(gomega.HaveOccurred())
212208
})
213209

@@ -263,82 +259,70 @@ var _ = ginkgo.Describe("dirctl end-to-end tests", func() {
263259
})
264260

265261
ginkgo.Context("init command", func() {
266-
ginkgo.It("should generate a new peer ID", func() {
262+
ginkgo.It("should generate a new peer ID and save the key to specified output", func() {
263+
outputPath := filepath.Join(tempDir, "generated.key")
267264
var outputBuffer bytes.Buffer
268265

269266
initCmd := clicmd.RootCmd
270267
initCmd.SetOut(&outputBuffer)
271268
initCmd.SetArgs([]string{
272269
"network",
273270
"init",
271+
"--output",
272+
outputPath,
274273
})
275274

276275
err := initCmd.Execute()
277276
gomega.Expect(err).NotTo(gomega.HaveOccurred())
278277

279-
// Verify that the output is not empty
278+
// Verify that the output file exists
279+
_, err = os.Stat(outputPath)
280+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
281+
282+
// Verify file permissions
283+
info, err := os.Stat(outputPath)
284+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
285+
gomega.Expect(info.Mode().Perm()).To(gomega.Equal(os.FileMode(0o0600)))
286+
287+
// Verify that the output is not empty and is a valid peer ID
280288
output := strings.TrimSpace(outputBuffer.String())
281289
gomega.Expect(output).NotTo(gomega.BeEmpty())
290+
gomega.Expect(output).To(gomega.HavePrefix("12D3"))
282291

283-
// Store the first peer ID
284-
firstPeerID := output
285-
286-
// Run the command again to verify we get a different peer ID
287-
var secondBuffer bytes.Buffer
288-
initCmd.SetOut(&secondBuffer)
292+
// Verify that the generated key can be used with the info command
293+
var infoBuffer bytes.Buffer
294+
infoCmd := clicmd.RootCmd
295+
infoCmd.SetOut(&infoBuffer)
296+
infoCmd.SetArgs([]string{
297+
"network",
298+
"info",
299+
outputPath,
300+
})
289301

290-
err = initCmd.Execute()
302+
err = infoCmd.Execute()
291303
gomega.Expect(err).NotTo(gomega.HaveOccurred())
292304

293-
secondPeerID := strings.TrimSpace(secondBuffer.String())
294-
gomega.Expect(secondPeerID).NotTo(gomega.BeEmpty())
295-
296-
// Verify that we get different peer IDs each time
297-
gomega.Expect(secondPeerID).NotTo(gomega.Equal(firstPeerID))
305+
// Verify that info command returns the same peer ID
306+
infoOutput := strings.TrimSpace(infoBuffer.String())
307+
gomega.Expect(infoOutput).To(gomega.Equal(output))
298308
})
299309

300-
ginkgo.It("should generate valid peer IDs", func() {
310+
ginkgo.It("should fail when output directory doesn't exist and cannot be created", func() {
311+
// Try to write to a location that should fail
301312
var outputBuffer bytes.Buffer
302313

303314
initCmd := clicmd.RootCmd
304315
initCmd.SetOut(&outputBuffer)
305316
initCmd.SetArgs([]string{
306317
"network",
307318
"init",
319+
"--output",
320+
"/nonexistent/directory/key.pem",
308321
})
309322

310323
err := initCmd.Execute()
311-
gomega.Expect(err).NotTo(gomega.HaveOccurred())
312-
313-
output := strings.TrimSpace(outputBuffer.String())
314-
315-
// Verify the peer ID format
316-
// Peer IDs typically start with "12D3" in base58 encoding
317-
gomega.Expect(output).To(gomega.HavePrefix("12D3"))
318-
gomega.Expect(len(output)).To(gomega.BeNumerically(">", 40)) // Peer IDs should be reasonably long
324+
gomega.Expect(err).To(gomega.HaveOccurred())
319325
})
320326
})
321327
})
322328
})
323-
324-
func generateED25519OpenSSLKey() ([]byte, error) {
325-
// Generate ED25519 key pair
326-
_, priv, err := ed25519.GenerateKey(rand.Reader)
327-
if err != nil {
328-
return nil, fmt.Errorf("failed to generate ED25519 key pair: %w", err)
329-
}
330-
331-
// Marshal the private key to PKCS#8 format
332-
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
333-
if err != nil {
334-
return nil, fmt.Errorf("failed to marshal private key: %w", err)
335-
}
336-
337-
// Create PEM block
338-
pemBlock := &pem.Block{
339-
Type: "PRIVATE KEY",
340-
Bytes: privBytes,
341-
}
342-
343-
return pem.EncodeToMemory(pemBlock), nil
344-
}

0 commit comments

Comments
 (0)