Skip to content

Commit

Permalink
Support managing plugin keys (#168)
Browse files Browse the repository at this point in the history
* support plugin keys
* bump CI to go 1.18
* upgrade notation-go
* update plugins branch
* reduce plugin print verbosity
* bump notation-go
* make key and cert name required
* simplify NewSignerFromFiles
* Apply suggestions from code review
* remove unused package
* fix plugin aliases
* return printkey errors

Signed-off-by: qmuntal <qmuntaldiaz@microsoft.com>

Co-authored-by: Shiwei Zhang <shizh@microsoft.com>
  • Loading branch information
qmuntal and shizhMSFT authored May 18, 2022
1 parent 0580780 commit f1173eb
Show file tree
Hide file tree
Showing 17 changed files with 370 additions and 266 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
go-version: [1.17]
go-version: [1.18]
fail-fast: true
steps:
- name: Set up Go ${{ matrix.go-version }}
Expand Down
42 changes: 12 additions & 30 deletions cmd/notation/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"

"github.com/notaryproject/notation-go/crypto/cryptoutil"
"github.com/notaryproject/notation/internal/ioutil"
"github.com/notaryproject/notation/internal/slices"
"github.com/notaryproject/notation/pkg/config"
"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -96,9 +99,6 @@ func addCert(ctx *cli.Context) error {
return err
}
name := ctx.String("name")
if name == "" {
name = nameFromPath(path)
}

// check if the target path is a cert
if _, err := cryptoutil.ReadCertificateFile(path); err != nil {
Expand All @@ -123,9 +123,13 @@ func addCert(ctx *cli.Context) error {
}

func addCertCore(cfg *config.File, name, path string) error {
if ok := cfg.VerificationCertificates.Certificates.Append(name, path); !ok {
if slices.Contains(cfg.VerificationCertificates.Certificates, name) {
return errors.New(name + ": already exists")
}
cfg.VerificationCertificates.Certificates = append(cfg.VerificationCertificates.Certificates, config.CertificateReference{
Name: name,
Path: path,
})
return nil
}

Expand All @@ -137,8 +141,7 @@ func listCerts(ctx *cli.Context) error {
}

// write out
printCertificateSet(cfg.VerificationCertificates.Certificates)
return nil
return ioutil.PrintCertificateMap(os.Stdout, cfg.VerificationCertificates.Certificates)
}

func removeCerts(ctx *cli.Context) error {
Expand All @@ -156,9 +159,11 @@ func removeCerts(ctx *cli.Context) error {

var removedNames []string
for _, name := range names {
if ok := cfg.VerificationCertificates.Certificates.Remove(name); !ok {
idx := slices.Index(cfg.VerificationCertificates.Certificates, name)
if idx < 0 {
return errors.New(name + ": not found")
}
cfg.VerificationCertificates.Certificates = slices.Delete(cfg.VerificationCertificates.Certificates, idx)
removedNames = append(removedNames, name)
}
if err := cfg.Save(); err != nil {
Expand All @@ -171,26 +176,3 @@ func removeCerts(ctx *cli.Context) error {
}
return nil
}

func printCertificateSet(s config.CertificateMap) {
maxNameSize := 0
for _, ref := range s {
if len(ref.Name) > maxNameSize {
maxNameSize = len(ref.Name)
}
}
format := fmt.Sprintf("%%-%ds\t%%s\n", maxNameSize)
fmt.Printf(format, "NAME", "PATH")
for _, ref := range s {
fmt.Printf(format, ref.Name, ref.Path)
}
}

func nameFromPath(path string) string {
base := filepath.Base(path)
name := base[:len(base)-len(filepath.Ext(base))]
if name == "" {
return base
}
return name
}
6 changes: 4 additions & 2 deletions cmd/notation/cert_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ func generateTestCert(ctx *cli.Context) error {
if err != nil {
return err
}
isDefaultKey, err := addKeyCore(cfg, name, keyPath, certPath, ctx.Bool(keyDefaultFlag.Name))
isDefault := ctx.Bool(keyDefaultFlag.Name)
keySuite := config.KeySuite{Name: name, X509KeyPair: &config.X509KeyPair{KeyPath: keyPath, CertificatePath: certPath}}
err = addKeyCore(cfg, keySuite, ctx.Bool(keyDefaultFlag.Name))
if err != nil {
return err
}
Expand All @@ -79,7 +81,7 @@ func generateTestCert(ctx *cli.Context) error {

// write out
fmt.Printf("%s: added to the key list\n", name)
if isDefaultKey {
if isDefault {
fmt.Printf("%s: marked as default\n", name)
}
if trust {
Expand Down
155 changes: 89 additions & 66 deletions cmd/notation/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import (
"crypto/tls"
"errors"
"fmt"
"os"
"path/filepath"

"github.com/notaryproject/notation-go/plugin/manager"
"github.com/notaryproject/notation/internal/ioutil"
"github.com/notaryproject/notation/internal/slices"
"github.com/notaryproject/notation/pkg/config"
"github.com/urfave/cli/v2"
)
Expand All @@ -31,12 +35,22 @@ var (
keyAddCommand = &cli.Command{
Name: "add",
Usage: "Add key to signing key list",
ArgsUsage: "<key_path> <cert_path>",
ArgsUsage: "[<key_path> <cert_path>]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "key name",
Name: "name",
Aliases: []string{"n"},
Usage: "key name",
Required: true,
},
&cli.StringFlag{
Name: "plugin",
Aliases: []string{"p"},
Usage: "signing plugin name",
},
&cli.StringFlag{
Name: "id",
Usage: "key id (required if --plugin is set)",
},
keyDefaultFlag,
},
Expand Down Expand Up @@ -71,63 +85,97 @@ var (
)

func addKey(ctx *cli.Context) error {
// initialize
args := ctx.Args()
switch args.Len() {
case 0:
return errors.New("missing key and certificate paths")
case 1:
return errors.New("missing certificate path for the correspoding key")
}

keyPath, err := filepath.Abs(args.Get(0))
if err != nil {
return err
}
certPath, err := filepath.Abs(args.Get(1))
cfg, err := config.LoadOrDefault()
if err != nil {
return err
}
var key config.KeySuite
pluginName := ctx.String("plugin")
name := ctx.String("name")
if name == "" {
name = nameFromPath(keyPath)
}

// check key / cert pair
if _, err := tls.LoadX509KeyPair(certPath, keyPath); err != nil {
return err
if pluginName != "" {
key, err = addExternalKey(ctx, pluginName, name)
} else {
key, err = newX509KeyPair(ctx, name)
}

// core process
cfg, err := config.LoadOrDefault()
if err != nil {
return err
}
isDefault, err := addKeyCore(cfg, name, keyPath, certPath, ctx.Bool(keyDefaultFlag.Name))

isDefault := ctx.Bool(keyDefaultFlag.Name)
err = addKeyCore(cfg, key, isDefault)
if err != nil {
return err
}

if err := cfg.Save(); err != nil {
return err
}

// write out
if isDefault {
fmt.Printf("%s: marked as default\n", name)
fmt.Printf("%s: marked as default\n", key.Name)
} else {
fmt.Println(name)
fmt.Println(key.Name)
}
return nil
}

func addKeyCore(cfg *config.File, name, keyPath, certPath string, markDefault bool) (bool, error) {
if ok := cfg.SigningKeys.Keys.Append(name, keyPath, certPath); !ok {
return false, errors.New(name + ": already exists")
func addExternalKey(ctx *cli.Context, pluginName, keyName string) (config.KeySuite, error) {
id := ctx.String("id")
if id == "" {
return config.KeySuite{}, errors.New("missing key id")
}
mgr := manager.NewManager()
p, err := mgr.Get(ctx.Context, pluginName)
if err != nil {
return config.KeySuite{}, err
}
if p.Err != nil {
return config.KeySuite{}, fmt.Errorf("invalid plugin: %w", p.Err)
}
return config.KeySuite{
Name: keyName,
ExternalKey: &config.ExternalKey{ID: id, PluginName: pluginName},
}, nil
}

func newX509KeyPair(ctx *cli.Context, keyName string) (config.KeySuite, error) {
args := ctx.Args()
switch args.Len() {
case 0:
return config.KeySuite{}, errors.New("missing key and certificate paths")
case 1:
return config.KeySuite{}, errors.New("missing certificate path for the corresponding key")
}

keyPath, err := filepath.Abs(args.Get(0))
if err != nil {
return config.KeySuite{}, err
}
certPath, err := filepath.Abs(args.Get(1))
if err != nil {
return config.KeySuite{}, err
}

// check key / cert pair
if _, err := tls.LoadX509KeyPair(certPath, keyPath); err != nil {
return config.KeySuite{}, err
}
return config.KeySuite{
Name: keyName,
X509KeyPair: &config.X509KeyPair{KeyPath: keyPath, CertificatePath: certPath},
}, nil
}

func addKeyCore(cfg *config.File, key config.KeySuite, markDefault bool) error {
if slices.Contains(cfg.SigningKeys.Keys, key.Name) {
return errors.New(key.Name + ": already exists")
}
cfg.SigningKeys.Keys = append(cfg.SigningKeys.Keys, key)
if markDefault {
cfg.SigningKeys.Default = name
cfg.SigningKeys.Default = key.Name
}
return cfg.SigningKeys.Default == name, nil
return nil
}

func updateKey(ctx *cli.Context) error {
Expand All @@ -142,7 +190,7 @@ func updateKey(ctx *cli.Context) error {
if err != nil {
return err
}
if _, _, ok := cfg.SigningKeys.Keys.Get(name); !ok {
if !slices.Contains(cfg.SigningKeys.Keys, name) {
return errors.New(name + ": not found")
}
if !ctx.Bool(keyDefaultFlag.Name) {
Expand All @@ -168,8 +216,7 @@ func listKeys(ctx *cli.Context) error {
}

// write out
printKeySet(cfg.SigningKeys.Default, cfg.SigningKeys.Keys)
return nil
return ioutil.PrintKeyMap(os.Stdout, cfg.SigningKeys.Default, cfg.SigningKeys.Keys)
}

func removeKeys(ctx *cli.Context) error {
Expand All @@ -188,9 +235,11 @@ func removeKeys(ctx *cli.Context) error {
prevDefault := cfg.SigningKeys.Default
var removedNames []string
for _, name := range names {
if ok := cfg.SigningKeys.Keys.Remove(name); !ok {
idx := slices.Index(cfg.SigningKeys.Keys, name)
if idx < 0 {
return errors.New(name + ": not found")
}
cfg.SigningKeys.Keys = slices.Delete(cfg.SigningKeys.Keys, idx)
removedNames = append(removedNames, name)
if prevDefault == name {
cfg.SigningKeys.Default = ""
Expand All @@ -210,29 +259,3 @@ func removeKeys(ctx *cli.Context) error {
}
return nil
}

func printKeySet(target string, s config.KeyMap) {
if len(s) == 0 {
fmt.Println("NAME\tPATH")
return
}

var maxNameSize, maxKeyPathSize int
for _, ref := range s {
if len(ref.Name) > maxNameSize {
maxNameSize = len(ref.Name)
}
if len(ref.KeyPath) > maxKeyPathSize {
maxKeyPathSize = len(ref.KeyPath)
}
}
format := fmt.Sprintf("%%c %%-%ds\t%%-%ds\t%%s\n", maxNameSize, maxKeyPathSize)
fmt.Printf(format, ' ', "NAME", "KEY PATH", "CERTIFICATE PATH")
for _, ref := range s {
mark := ' '
if ref.Name == target {
mark = '*'
}
fmt.Printf(format, mark, ref.Name, ref.KeyPath, ref.CertificatePath)
}
}
1 change: 1 addition & 0 deletions cmd/notation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func main() {
certCommand,
keyCommand,
cacheCommand,
pluginCommand,
},
}
if err := app.Run(os.Args); err != nil {
Expand Down
35 changes: 35 additions & 0 deletions cmd/notation/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"os"

"github.com/notaryproject/notation-go/plugin/manager"
"github.com/notaryproject/notation/internal/ioutil"
"github.com/urfave/cli/v2"
)

var (
pluginCommand = &cli.Command{
Name: "plugin",
Usage: "Manage plugins",
Subcommands: []*cli.Command{
pluginListCommand,
},
}

pluginListCommand = &cli.Command{
Name: "list",
Usage: "List registered plugins",
Aliases: []string{"ls"},
Action: listPlugins,
}
)

func listPlugins(ctx *cli.Context) error {
mgr := manager.NewManager()
plugins, err := mgr.List(ctx.Context)
if err != nil {
return err
}
return ioutil.PrintPlugins(os.Stdout, plugins)
}
Loading

0 comments on commit f1173eb

Please sign in to comment.