Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ for JWT signature verification.
`,
RunE: keygenMain,
SilenceUsage: true,
Deprecated: "This command will be removed in a future release. Use `pelican key create` instead.",
}

outPasswordPath string
Expand All @@ -66,6 +67,6 @@ func init() {
passwordCmd.Flags().StringVarP(&outPasswordPath, "output", "o", "", "The path to the generate htpasswd password file. Default: ./server-web-passwd")
passwordCmd.Flags().StringVarP(&inPasswordPath, "password", "p", "", "The path to the file containing the password. Will take from terminal input if not provided")

keygenCmd.Flags().StringVar(&privateKeyPath, "private-key", "", "The path to the generate private key file. Default: ./issuer.jwk")
keygenCmd.Flags().StringVar(&privateKeyPath, "private-key", "", "The path to the generate private key file. Default: ./private-key.pem")
keygenCmd.Flags().StringVar(&publicKeyPath, "public-key", "", "The path to the generate public key file. Default: ./issuer-pub.jwks")
}
32 changes: 32 additions & 0 deletions cmd/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"github.com/spf13/cobra"
)

var (
keyCmd = &cobra.Command{
Use: "key",
Short: "Manage Pelican issuer keys",
}

// `pelican key create` command aims to replace `pelican generate keygen` command in the future.
// For now, they are both available and do the same thing to maintain backward compatibility.
keyCreateCmd = &cobra.Command{
Use: "create",
Short: "Generate a public-private key-pair for Pelican server",
Long: `Generate a public-private key-pair for a Pelican server.
The private key is an ECDSA key with P256 curve. The corresponding public key
is a JSON Web Key Set (JWKS), which can be used for JWT signature verification.`,
RunE: keygenMain,
SilenceUsage: true,
}
)

func init() {
keyCmd.AddCommand(keyCreateCmd)

// Attach flags to the `create` sub-command
keyCreateCmd.Flags().StringVar(&privateKeyPath, "private-key", "./private-key.pem", "The file path where the generated private key will be saved. If a key already exists at the provided path, it will not be overwritten but will be used to derive a public key")
keyCreateCmd.Flags().StringVar(&publicKeyPath, "public-key", "./issuer-pub.jwks", "The file path where the generated public key (derived from the generated private key) will be saved.")
}
23 changes: 16 additions & 7 deletions cmd/generate_keygen.go → cmd/key_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/pelicanplatform/pelican/config"
Expand All @@ -54,7 +55,7 @@ func keygenMain(cmd *cobra.Command, args []string) error {
return errors.Wrap(err, "failed to get the current working directory")
}
if privateKeyPath == "" {
privateKeyPath = filepath.Join(wd, "issuer.jwk")
privateKeyPath = filepath.Join(wd, "private-key.pem")
} else {
privateKeyPath = filepath.Clean(strings.TrimSpace(privateKeyPath))
}
Expand All @@ -73,18 +74,26 @@ func keygenMain(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "failed to create directory for public key at %s", filepath.Dir(publicKeyPath))
}

_, err = os.Stat(privateKeyPath)
// Check if public key file exists; if so, fail
_, err = os.Stat(publicKeyPath)
if err == nil {
return fmt.Errorf("file exists for private key under %s", privateKeyPath)
return fmt.Errorf("file exists for public key under %s", publicKeyPath)
}

_, err = os.Stat(publicKeyPath)
// Check if private key file exists
privKeyExists := false
_, err = os.Stat(privateKeyPath)
if err == nil {
return fmt.Errorf("file exists for public key under %s", publicKeyPath)
privKeyExists = true
log.Warnf("Private key file already exists at %s. Using existing key to generate public key.", privateKeyPath)
} else if !os.IsNotExist(err) {
return errors.Wrapf(err, "error checking private key at %s", privateKeyPath)
}

if err := config.GeneratePrivateKey(privateKeyPath, elliptic.P256(), false); err != nil {
return errors.Wrapf(err, "failed to generate new private key at %s", privateKeyPath)
if !privKeyExists {
if err := config.GeneratePrivateKey(privateKeyPath, elliptic.P256(), false); err != nil {
return errors.Wrapf(err, "failed to generate new private key at %s", privateKeyPath)
}
}

privKey, err := config.LoadSinglePEM(privateKeyPath)
Expand Down
47 changes: 38 additions & 9 deletions cmd/generate_keygen_test.go → cmd/key_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestKeygenMain(t *testing.T) {

checkKeys(
t,
filepath.Join(tempDir, "issuer.jwk"),
filepath.Join(tempDir, "private-key.pem"),
filepath.Join(tempDir, "issuer-pub.jwks"),
)
})
Expand Down Expand Up @@ -119,7 +119,7 @@ func TestKeygenMain(t *testing.T) {

checkKeys(
t,
filepath.Join(tempWd, "issuer.jwk"),
filepath.Join(tempWd, "private-key.pem"),
publicKeyPath,
)
})
Expand Down Expand Up @@ -153,23 +153,51 @@ func TestKeygenMain(t *testing.T) {

checkKeys(
t,
filepath.Join(tempWd, "issuer.jwk"),
filepath.Join(tempWd, "private-key.pem"),
publicKeyPath,
)
})
}

func TestKeygenMainWithExistingFile(t *testing.T) {
server_utils.ResetTestState()
t.Cleanup(func() {
server_utils.ResetTestState()
})

// If a private key file exists, the derived public key file should be created by the command
t.Run("private-key-exists", func(t *testing.T) {
tempDir := t.TempDir()
publicKeyPath = filepath.Join(tempDir, "issuer-pub.jwks")

err := os.WriteFile(filepath.Join(tempDir, "test.pk"), []byte{}, 0644)
// Create a private key file
privateKey, err := config.GeneratePEM(tempDir)
require.NoError(t, err)
privateKeyPath = filepath.Join(tempDir, "test.pk")
publicKeyPath = filepath.Join(tempDir, "test.pub")
assert.NotEmpty(t, privateKey.KeyID())

// There should be only one file in the temp directory, which is the private key file
files, err := os.ReadDir(tempDir)
require.NoError(t, err)
require.Len(t, files, 1)
// Set the private key path to the existing private key file
privateKeyPath = filepath.Join(tempDir, files[0].Name())

err = keygenMain(nil, []string{})
require.Error(t, err)
assert.Contains(t, err.Error(), "file exists")
require.NoError(t, err)

// Check that the public key file is created
assert.FileExists(t, filepath.Join(tempDir, "issuer-pub.jwks"))

// Check that the public key file contains the correct key
jwks, err := jwk.ReadFile(filepath.Join(tempDir, "issuer-pub.jwks"))
require.NoError(t, err)
assert.Equal(t, 1, jwks.Len())
key, ok := jwks.Key(0)
assert.True(t, ok)
assert.Equal(t, privateKey.KeyID(), key.KeyID())
})

// If a public key file exists, the command should fail
t.Run("public-key-exists", func(t *testing.T) {
tempDir := t.TempDir()
err := os.WriteFile(filepath.Join(tempDir, "test.pub"), []byte{}, 0644)
Expand All @@ -178,6 +206,7 @@ func TestKeygenMain(t *testing.T) {
publicKeyPath = filepath.Join(tempDir, "test.pub")
err = keygenMain(nil, []string{})
require.Error(t, err)
assert.Contains(t, err.Error(), "file exists")
assert.Contains(t, err.Error(), "file exists for public key")
})

}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func init() {
rootCmd.AddCommand(rootPluginCmd)
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(generateCmd)
rootCmd.AddCommand(keyCmd)
rootCmd.AddCommand(downtimeCmd)
rootCmd.AddCommand(config_printer.ConfigCmd)
preferredPrefix := config.GetPreferredPrefix()
Expand Down
Loading