From 1a67a8d3ba841e0200beb0952c115047606da69f Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Tue, 3 Oct 2023 00:43:27 +0200 Subject: [PATCH] update `kes-go` dependency to v0.2.0 This commit updates the Go SDK used to implement the CLI to v0.2.0. This mainly includes changes to listing via the `kes.ListIter[T]` and switching to `kes.Rule` based `kes.Policy` type. Signed-off-by: Andreas Auernhammer --- cmd/kes/gateway.go | 17 +- cmd/kes/identity.go | 84 ++--- cmd/kes/key.go | 122 ++----- cmd/kes/main.go | 2 - cmd/kes/policy.go | 98 +++--- cmd/kes/secret.go | 543 ------------------------------- cmd/kes/server.go | 6 +- go.mod | 2 +- go.sum | 4 +- internal/api/identity.go | 35 +- internal/api/key.go | 17 +- internal/api/policy.go | 8 +- internal/auth/policy.go | 52 +-- internal/key/ciphertext.go | 4 +- internal/key/key.go | 27 +- internal/key/key_test.go | 37 +-- internal/keystore/kes/kes.go | 33 +- kestest/gateway_aws_test.go | 1 - kestest/gateway_azure_test.go | 1 - kestest/gateway_entrust_test.go | 1 - kestest/gateway_fortanix_test.go | 1 - kestest/gateway_fs_test.go | 1 - kestest/gateway_gcp_test.go | 1 - kestest/gateway_gemalto_test.go | 1 - kestest/gateway_mem_test.go | 1 - kestest/gateway_test.go | 196 +++-------- kestest/gateway_vault_test.go | 1 - kestest/policy.go | 6 +- 28 files changed, 245 insertions(+), 1057 deletions(-) delete mode 100644 cmd/kes/secret.go diff --git a/cmd/kes/gateway.go b/cmd/kes/gateway.go index 7936c70e..b81f47a9 100644 --- a/cmd/kes/gateway.go +++ b/cmd/kes/gateway.go @@ -216,9 +216,18 @@ func policySetFromConfig(config *edge.ServerConfig) (auth.PolicySet, error) { return nil, fmt.Errorf("policy %q already exists", name) } + allow := make(map[string]kes.Rule, len(policy.Allow)) + for _, pattern := range policy.Allow { + allow[pattern] = kes.Rule{} + } + deny := make(map[string]kes.Rule, len(policy.Deny)) + for _, pattern := range policy.Deny { + deny[pattern] = kes.Rule{} + } + policies.policies[name] = &auth.Policy{ - Allow: policy.Allow, - Deny: policy.Deny, + Allow: allow, + Deny: deny, CreatedAt: time.Now().UTC(), CreatedBy: config.Admin, } @@ -593,9 +602,9 @@ func newGatewayConfig(ctx context.Context, config *edge.ServerConfig, tlsConfig for _, k := range config.Keys { var algorithm kes.KeyAlgorithm if fips.Enabled || cpu.HasAESGCM() { - algorithm = kes.AES256_GCM_SHA256 + algorithm = kes.AES256 } else { - algorithm = kes.XCHACHA20_POLY1305 + algorithm = kes.ChaCha20 } key, err := key.Random(algorithm, config.Admin) diff --git a/cmd/kes/identity.go b/cmd/kes/identity.go index 1b0fc8da..e135f4dd 100644 --- a/cmd/kes/identity.go +++ b/cmd/kes/identity.go @@ -11,13 +11,15 @@ import ( "crypto/sha256" "crypto/x509" "encoding/hex" + "encoding/json" "encoding/pem" "errors" "fmt" + "io" "net" "os" "os/signal" - "sort" + "slices" "strings" "time" @@ -416,8 +418,8 @@ func infoIdentityCmd(args []string) { fmt.Println(faint.Render(fmt.Sprintf("%-11s", "Created By")), info.CreatedBy) } if info.Policy != "" { - year, month, day := policy.Info.CreatedAt.Date() - hour, min, sec := policy.Info.CreatedAt.Clock() + year, month, day := policy.CreatedAt.Date() + hour, min, sec := policy.CreatedAt.Clock() fmt.Println() fmt.Println(faint.Render(fmt.Sprintf("%-11s", "Policy")), policyStyle.Render(info.Policy)) @@ -513,71 +515,47 @@ func lsIdentityCmd(args []string) { cli.Fatal("too many arguments. See 'kes identity ls --help'") } - pattern := "*" + var prefix string if cmd.NArg() == 1 { - pattern = cmd.Arg(0) + prefix = cmd.Arg(0) } ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() enclave := newEnclave(enclaveName, insecureSkipVerify) - identities, err := enclave.ListIdentities(ctx, pattern) - if err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to list identities: %v", err) + iter := &kes.ListIter[kes.Identity]{ + NextFunc: enclave.ListIdentities, } - defer identities.Close() - if jsonFlag { - if _, err = identities.WriteTo(os.Stdout); err != nil { - cli.Fatal(err) - } - if err = identities.Close(); err != nil { - cli.Fatal(err) - } - } else { - sortedInfos, err := identities.Values(0) + var ids []kes.Identity + for id, err := iter.SeekTo(ctx, prefix); err != io.EOF; id, err = iter.Next(ctx) { if err != nil { cli.Fatalf("failed to list identities: %v", err) } - if len(sortedInfos) > 0 { - sort.Slice(sortedInfos, func(i, j int) bool { - return strings.Compare(sortedInfos[i].Policy, sortedInfos[j].Policy) < 0 - }) - - headerStyle := tui.NewStyle() - dateStyle := tui.NewStyle() - policyStyle := tui.NewStyle() - if colorFlag.Colorize() { - const ( - ColorDate tui.Color = "#5f8700" - ColorPolicy tui.Color = "#2E42D1" - ) - headerStyle = headerStyle.Underline(true).Bold(true) - dateStyle = dateStyle.Foreground(ColorDate) - policyStyle = policyStyle.Foreground(ColorPolicy) - } + ids = append(ids, id) + } + slices.Sort(ids) - fmt.Printf("%s %s %s\n", - headerStyle.Render(fmt.Sprintf("%-19s", "Date Created")), - headerStyle.Render(fmt.Sprintf("%-64s", "Identity")), - headerStyle.Render("Policy"), - ) - for _, info := range sortedInfos { - year, month, day := info.CreatedAt.Local().Date() - hour, min, sec := info.CreatedAt.Local().Clock() - - fmt.Printf("%s %s %s\n", - dateStyle.Render(fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec)), - fmt.Sprintf("%-64s", info.Identity.String()), - policyStyle.Render(fmt.Sprintf("%-15s", info.Policy)), - ) - } + if jsonFlag { + if err := json.NewEncoder(os.Stdout).Encode(ids); err != nil { + cli.Fatalf("failed to list identities: %v", err) } } + if len(ids) == 0 { + return + } + + var ( + style = tui.NewStyle().Underline(colorFlag.Colorize()) + buf = &strings.Builder{} + ) + fmt.Fprintln(buf, style.Render("Identity")) + for _, id := range ids { + buf.WriteString(id.String()) + buf.WriteByte('\n') + } + fmt.Print(buf) } const rmIdentityCmdUsage = `Usage: diff --git a/cmd/kes/key.go b/cmd/kes/key.go index 673f621d..3440cfcf 100644 --- a/cmd/kes/key.go +++ b/cmd/kes/key.go @@ -10,9 +10,10 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "os/signal" - "sort" + "slices" "strings" tui "github.com/charmbracelet/lipgloss" @@ -174,7 +175,7 @@ func importKeyCmd(args []string) { defer cancel() enclave := newEnclave(enclaveName, insecureSkipVerify) - if err = enclave.ImportKey(ctx, name, key); err != nil { + if err = enclave.ImportKey(ctx, name, &kes.ImportKeyRequest{Key: key}); err != nil { if errors.Is(err, context.Canceled) { os.Exit(1) } @@ -248,46 +249,15 @@ func describeKeyCmd(args []string) { return } - var faint, nameStyle tui.Style - if colorFlag.Colorize() { - const ColorName tui.Color = "#2e42d1" - faint = faint.Faint(true).Bold(true) - nameStyle = nameStyle.Foreground(ColorName) - } year, month, day := info.CreatedAt.Date() hour, min, sec := info.CreatedAt.Clock() - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Name")), - nameStyle.Render(info.Name), - ) - if info.ID != "" { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "ID")), - info.ID, - ) - } - if info.Algorithm != kes.KeyAlgorithmUndefined { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Algorithm")), - info.Algorithm, - ) - } - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created At")), - fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec), - ) - if info.CreatedBy.IsUnknown() { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created By")), - "", - ) - } else { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created By")), - info.CreatedBy, - ) - } + buf := &strings.Builder{} + fmt.Fprintf(buf, "%-11s %s\n", "Name", info.Name) + fmt.Fprintf(buf, "%-11s %s\n", "Algorithm", info.Algorithm) + fmt.Fprintf(buf, "%-11s %04d-%02d-%02d %02d:%02d:%02d\n", "Date", year, month, day, hour, min, sec) + fmt.Fprintf(buf, "%-11s %s", "Owner", info.CreatedBy) + fmt.Print(buf) } const lsKeyCmdUsage = `Usage: @@ -335,71 +305,47 @@ func lsKeyCmd(args []string) { cli.Fatal("too many arguments. See 'kes key ls --help'") } - pattern := "*" + prefix := "*" if cmd.NArg() == 1 { - pattern = cmd.Arg(0) + prefix = cmd.Arg(0) } ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() enclave := newEnclave(enclaveName, insecureSkipVerify) - iterator, err := enclave.ListKeys(ctx, pattern) - if err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to list keys: %v", err) + iter := &kes.ListIter[string]{ + NextFunc: enclave.ListKeys, } - defer iterator.Close() - if jsonFlag { - if _, err = iterator.WriteTo(os.Stdout); err != nil { - cli.Fatal(err) - } - if err = iterator.Close(); err != nil { - cli.Fatal(err) - } - } else { - keys, err := iterator.Values(0) + var names []string + for id, err := iter.SeekTo(ctx, prefix); err != io.EOF; id, err = iter.Next(ctx) { if err != nil { cli.Fatalf("failed to list keys: %v", err) } - if err = iterator.Close(); err != nil { - cli.Fatalf("failed to list keys: %v", err) - } - - if len(keys) > 0 { - sort.Slice(keys, func(i, j int) bool { - return strings.Compare(keys[i].Name, keys[j].Name) < 0 - }) - - headerStyle := tui.NewStyle() - dateStyle := tui.NewStyle() - if colorFlag.Colorize() { - const ColorDate tui.Color = "#5f8700" - headerStyle = headerStyle.Underline(true).Bold(true) - dateStyle = dateStyle.Foreground(ColorDate) - } + names = append(names, id) + } + slices.Sort(names) - fmt.Println( - headerStyle.Render(fmt.Sprintf("%-19s", "Date Created")), - headerStyle.Render("Key"), - ) - for _, key := range keys { - var date string - if key.CreatedAt.IsZero() { - date = fmt.Sprintf("%5s%s%5s", " ", "", " ") - } else { - year, month, day := key.CreatedAt.Local().Date() - hour, min, sec := key.CreatedAt.Local().Clock() - date = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec) - } - fmt.Printf("%s %s\n", dateStyle.Render(date), key.Name) - } + if jsonFlag { + if err := json.NewEncoder(os.Stdout).Encode(names); err != nil { + cli.Fatalf("failed to list keys: %v", err) } + } + if len(names) == 0 { + return + } + var ( + style = tui.NewStyle().Underline(colorFlag.Colorize()) + buf = &strings.Builder{} + ) + fmt.Fprintln(buf, style.Render("Key")) + for _, name := range names { + buf.WriteString(name) + buf.WriteByte('\n') } + fmt.Print(buf) } const rmKeyCmdUsage = `Usage: diff --git a/cmd/kes/main.go b/cmd/kes/main.go index 93172406..702ee17d 100644 --- a/cmd/kes/main.go +++ b/cmd/kes/main.go @@ -31,7 +31,6 @@ Commands: server Start a KES server. key Manage cryptographic keys. - secret Manage KES secrets. policy Manage KES policies. identity Manage KES identities. @@ -60,7 +59,6 @@ func main() { "server": serverCmd, "key": keyCmd, - "secret": secretCmd, "policy": policyCmd, "identity": identityCmd, diff --git a/cmd/kes/policy.go b/cmd/kes/policy.go index 9bb52e5d..9351a724 100644 --- a/cmd/kes/policy.go +++ b/cmd/kes/policy.go @@ -9,9 +9,9 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "os/signal" - "sort" "strings" "time" @@ -110,64 +110,46 @@ func lsPolicyCmd(args []string) { cli.Fatal("too many arguments. See 'kes policy ls --help'") } - pattern := "*" + prefix := "*" if cmd.NArg() == 1 { - pattern = cmd.Arg(0) + prefix = cmd.Arg(0) } ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() enclave := newEnclave(enclaveName, insecureSkipVerify) - policies, err := enclave.ListPolicies(ctx, pattern) - if err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to list policies: %v", err) + iter := &kes.ListIter[string]{ + NextFunc: enclave.ListPolicies, } - defer policies.Close() - if jsonFlag { - if _, err = policies.WriteTo(os.Stdout); err != nil { - cli.Fatal(err) - } - if err = policies.Close(); err != nil { - cli.Fatal(err) - } - } else { - sortedInfos, err := policies.Values(0) + var names []string + for id, err := iter.SeekTo(ctx, prefix); err != io.EOF; id, err = iter.Next(ctx) { if err != nil { - cli.Fatalf("failed to list policies: %v", err) + cli.Fatalf("failed to list keys: %v", err) } - if len(sortedInfos) > 0 { - sort.Slice(sortedInfos, func(i, j int) bool { - return strings.Compare(sortedInfos[i].Name, sortedInfos[j].Name) < 0 - }) - - headerStyle := tui.NewStyle() - dateStyle := tui.NewStyle() - if colorFlag.Colorize() { - const ColorDate tui.Color = "#5f8700" - headerStyle = headerStyle.Underline(true).Bold(true) - dateStyle = dateStyle.Foreground(ColorDate) - } + names = append(names, id) + } - fmt.Println( - headerStyle.Render(fmt.Sprintf("%-19s", "Date Created")), - headerStyle.Render("Policy"), - ) - for _, info := range sortedInfos { - year, month, day := info.CreatedAt.Local().Date() - hour, min, sec := info.CreatedAt.Local().Clock() - - fmt.Printf("%s %s\n", - dateStyle.Render(fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec)), - info.Name, - ) - } + if jsonFlag { + if err := json.NewEncoder(os.Stdout).Encode(names); err != nil { + cli.Fatalf("failed to list keys: %v", err) } } + if len(names) == 0 { + return + } + + var ( + style = tui.NewStyle().Underline(colorFlag.Colorize()) + buf = &strings.Builder{} + ) + fmt.Fprintln(buf, style.Render("Key")) + for _, name := range names { + buf.WriteString(name) + buf.WriteByte('\n') + } + fmt.Print(buf) } const rmPolicyCmdUsage = `Usage: @@ -354,10 +336,10 @@ func showPolicyCmd(args []string) { } if !isTerm(os.Stdout) || jsonFlag { type Response struct { - Allow []string `json:"allow,omitempty"` - Deny []string `json:"deny,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - CreatedBy kes.Identity `json:"created_by,omitempty"` + Allow map[string]kes.Rule `json:"allow,omitempty"` + Deny map[string]kes.Rule `json:"deny,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + CreatedBy kes.Identity `json:"created_by,omitempty"` } encoder := json.NewEncoder(os.Stdout) if isTerm(os.Stdout) { @@ -366,8 +348,8 @@ func showPolicyCmd(args []string) { err = encoder.Encode(Response{ Allow: policy.Allow, Deny: policy.Deny, - CreatedAt: policy.Info.CreatedAt, - CreatedBy: policy.Info.CreatedBy, + CreatedAt: policy.CreatedAt, + CreatedBy: policy.CreatedBy, }) if err != nil { cli.Fatalf("failed to show policy '%s': %v", name, err) @@ -381,7 +363,7 @@ func showPolicyCmd(args []string) { if len(policy.Allow) > 0 { header := tui.NewStyle().Bold(true).Foreground(Green) fmt.Println(header.Render("Allow:")) - for _, rule := range policy.Allow { + for rule := range policy.Allow { fmt.Println(" · " + rule) } } @@ -391,20 +373,20 @@ func showPolicyCmd(args []string) { } header := tui.NewStyle().Bold(true).Foreground(Red) fmt.Println(header.Render("Deny:")) - for _, rule := range policy.Deny { + for rule := range policy.Deny { fmt.Println(" · " + rule) } } fmt.Println() header := tui.NewStyle().Bold(true).Foreground(Cyan) - if !policy.Info.CreatedAt.IsZero() { - year, month, day := policy.Info.CreatedAt.Local().Date() - hour, min, sec := policy.Info.CreatedAt.Local().Clock() + if !policy.CreatedAt.IsZero() { + year, month, day := policy.CreatedAt.Local().Date() + hour, min, sec := policy.CreatedAt.Local().Clock() fmt.Printf("\n%s %04d-%02d-%02d %02d:%02d:%02d\n", header.Render("Created at:"), year, month, day, hour, min, sec) } - if !policy.Info.CreatedBy.IsUnknown() { - fmt.Println(header.Render("Created by:"), policy.Info.CreatedBy) + if !policy.CreatedBy.IsUnknown() { + fmt.Println(header.Render("Created by:"), policy.CreatedBy) } else { fmt.Println(header.Render("Created by:"), "") } diff --git a/cmd/kes/secret.go b/cmd/kes/secret.go deleted file mode 100644 index a368d81e..00000000 --- a/cmd/kes/secret.go +++ /dev/null @@ -1,543 +0,0 @@ -// Copyright 2022 - MinIO, Inc. All rights reserved. -// Use of this source code is governed by the AGPLv3 -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "os/signal" - "sort" - "strings" - "time" - - "aead.dev/mem" - tui "github.com/charmbracelet/lipgloss" - "github.com/minio/kes-go" - "github.com/minio/kes/internal/cli" - "github.com/minio/kes/internal/secret" - flag "github.com/spf13/pflag" - "golang.org/x/term" -) - -const secretCmdUsage = `Usage: - kes secret - -Commands: - create Create a new secret. - info Get information about a secret. - show Display a secret. - ls List secrets. - rm Delete a secret. - -Options: - -h, --help Print command line options. -` - -func secretCmd(args []string) { - cmd := flag.NewFlagSet(args[0], flag.ContinueOnError) - cmd.Usage = func() { fmt.Fprint(os.Stderr, secretCmdUsage) } - - subCmds := commands{ - "create": createSecretCmd, - "info": describeSecretCmd, - "show": showSecretCmd, - "ls": lsSecretCmd, - "rm": deleteSecretCmd, - } - - if len(args) < 2 { - cmd.Usage() - os.Exit(2) - } - if cmd, ok := subCmds[args[1]]; ok { - cmd(args[1:]) - return - } - - if err := cmd.Parse(args[1:]); err != nil { - if errors.Is(err, flag.ErrHelp) { - os.Exit(2) - } - cli.Fatalf("%v. See 'kes key --help'", err) - } - if cmd.NArg() > 0 { - cli.Fatalf("%q is not a key command. See 'kes key --help'", cmd.Arg(0)) - } - cmd.Usage() - os.Exit(2) -} - -const createSecretCmdUsage = `Usage: - kes secret create [options] - -Options: - -k, --insecure Skip TLS certificate validation. - -e, --enclave Operate within the specified enclave. - --file Use the file content as secret value. - - -h, --help Print command line options. - -Examples: - $ kes secret create my-secret - Enter secret: - - $ kes secret create my-secret password123 - $ kes secret create my-secret --file password.txt -` - -func createSecretCmd(args []string) { - cmd := flag.NewFlagSet(args[0], flag.ContinueOnError) - cmd.Usage = func() { fmt.Fprint(os.Stderr, createSecretCmdUsage) } - - var ( - insecureSkipVerify bool - enclaveName string - filename string - ) - cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation") - cmd.StringVarP(&enclaveName, "enclave", "e", "", "Operate within the specified enclave") - cmd.StringVar(&filename, "file", "", "Use the file contet as secret value") - if err := cmd.Parse(args[1:]); err != nil { - if errors.Is(err, flag.ErrHelp) { - os.Exit(2) - } - cli.Fatalf("%v. See 'kes secret create --help'", err) - } - - switch n := cmd.NArg(); { - case n == 0: - cli.Fatal("no secret name specified. See 'kes secret create --help'") - case n == 2 && filename != "": - cli.Fatalf("cannot read from '%s' when a secret value is specified. See 'kes secret create --help'", filename) - case n > 2: - cli.Fatal("too many arguments. See 'kes secret create --help'") - } - - var value []byte - switch { - case cmd.NArg() == 2: - value = []byte(cmd.Arg(1)) - case filename != "": - file, err := os.Open(filename) - if err != nil { - cli.Fatalf("failed to read '%s': %v", err) - } - defer file.Close() - - var buffer bytes.Buffer - if _, err = io.Copy(&buffer, mem.LimitReader(os.Stdin, secret.MaxSize)); err != nil { - cli.Fatalf("failed to read '%s': %v", err) - } - value = buffer.Bytes() - default: - fmt.Print("Enter secret: ") - secret, err := term.ReadPassword(int(os.Stdin.Fd())) - fmt.Println() - if err != nil { - cli.Fatalf("failed to read secret input: %v", err) - } - value = secret - } - - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) - defer cancel() - - name := cmd.Arg(0) - enclave := newEnclave(enclaveName, insecureSkipVerify) - if err := enclave.CreateSecret(ctx, name, value, nil); err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to create secret %q: %v", name, err) - } -} - -const describeSecretCmdUsage = `Usage: - kes secret info [options] - -Options: - -k, --insecure Skip TLS certificate validation. - --json Print keys in JSON format. - --color Specify when to use colored output. The automatic - mode only enables colors if an interactive terminal - is detected - colors are automatically disabled if - the output goes to a pipe. - Possible values: *auto*, never, always. - -e, --enclave Operate within the specified enclave. - - -h, --help Print command line options. - -Examples: - $ kes secret info my-secret -` - -func describeSecretCmd(args []string) { - cmd := flag.NewFlagSet(args[0], flag.ContinueOnError) - cmd.Usage = func() { fmt.Fprint(os.Stderr, describeSecretCmdUsage) } - - var ( - jsonFlag bool - colorFlag colorOption - insecureSkipVerify bool - enclaveName string - ) - cmd.BoolVar(&jsonFlag, "json", false, "Print identities in JSON format") - cmd.Var(&colorFlag, "color", "Specify when to use colored output") - cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation") - cmd.StringVarP(&enclaveName, "enclave", "e", "", "Operate within the specified enclave") - if err := cmd.Parse(args[1:]); err != nil { - if errors.Is(err, flag.ErrHelp) { - os.Exit(2) - } - cli.Fatalf("%v. See 'kes secret info --help'", err) - } - - switch { - case cmd.NArg() == 0: - cli.Fatal("no secret name specified. See 'kes secret info --help'") - case cmd.NArg() > 1: - cli.Fatal("too many arguments. See 'kes secret info --help'") - } - - ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) - defer cancelCtx() - - name := cmd.Arg(0) - enclave := newEnclave(enclaveName, insecureSkipVerify) - info, err := enclave.DescribeSecret(ctx, name) - if err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to describe keys: %v", err) - } - if jsonFlag { - if err = json.NewEncoder(os.Stdout).Encode(info); err != nil { - cli.Fatalf("failed to describe keys: %v", err) - } - return - } - - var faint, nameStyle tui.Style - if colorFlag.Colorize() { - const ColorName tui.Color = "#2e42d1" - faint = faint.Faint(true).Bold(true) - nameStyle = nameStyle.Foreground(ColorName) - } - year, month, day := info.CreatedAt.Date() - hour, min, sec := info.CreatedAt.Clock() - - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Name")), - nameStyle.Render(info.Name), - ) - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Type")), - info.Type.String(), - ) - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created At")), - fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec), - ) - if info.CreatedBy.IsUnknown() { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created By")), - "", - ) - } else { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created By")), - info.CreatedBy, - ) - } -} - -const showSecretCmdUsage = `Usage: - kes secret show [options] - -Options: - -k, --insecure Skip TLS certificate validation. - -p, --plain Print the raw secret without any styling. - --json Print the secret in JSON format. - --color Specify when to use colored output. The automatic - mode only enables colors if an interactive terminal - is detected - colors are automatically disabled if - the output goes to a pipe. - Possible values: *auto*, never, always. - -e, --enclave Operate within the specified enclave. - - -h, --help Print command line options. - -Examples: - $ kes secret show my-secret -` - -func showSecretCmd(args []string) { - cmd := flag.NewFlagSet(args[0], flag.ContinueOnError) - cmd.Usage = func() { fmt.Fprint(os.Stderr, showSecretCmdUsage) } - - var ( - plainFlag bool - jsonFlag bool - colorFlag colorOption - insecureSkipVerify bool - enclaveName string - ) - cmd.BoolVar(&jsonFlag, "json", false, "Print identities in JSON format") - cmd.Var(&colorFlag, "color", "Specify when to use colored output") - cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation") - cmd.BoolVarP(&plainFlag, "plain", "p", false, "Print the raw secret without any styling") - cmd.StringVarP(&enclaveName, "enclave", "e", "", "Operate within the specified enclave") - if err := cmd.Parse(args[1:]); err != nil { - if errors.Is(err, flag.ErrHelp) { - os.Exit(2) - } - cli.Fatalf("%v. See 'kes secret show --help'", err) - } - - switch { - case cmd.NArg() == 0: - cli.Fatal("no secret name specified. See 'kes secret show --help'") - case cmd.NArg() > 1: - cli.Fatal("too many arguments. See 'kes secret show --help'") - } - - ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) - defer cancelCtx() - - name := cmd.Arg(0) - enclave := newEnclave(enclaveName, insecureSkipVerify) - secret, info, err := enclave.ReadSecret(ctx, name) - if err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to describe keys: %v", err) - } - if plainFlag { - fmt.Println(string(secret)) - return - } - if jsonFlag { - type JSON struct { - Bytes []byte `json:"bytes"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at,omitempty"` - CreatedBy kes.Identity `json:"created_by,omitempty"` - } - err = json.NewEncoder(os.Stdout).Encode(JSON{ - Bytes: secret, - Name: info.Name, - CreatedAt: info.CreatedAt, - CreatedBy: info.CreatedBy, - }) - if err != nil { - cli.Fatalf("failed to describe keys: %v", err) - } - return - } - - var faint, nameStyle tui.Style - if colorFlag.Colorize() { - const ColorName tui.Color = "#2e42d1" - faint = faint.Faint(true).Bold(true) - nameStyle = nameStyle.Foreground(ColorName) - } - year, month, day := info.CreatedAt.Date() - hour, min, sec := info.CreatedAt.Clock() - - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Name")), - nameStyle.Render(info.Name), - ) - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Type")), - info.Type.String(), - ) - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created At")), - fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec), - ) - if info.CreatedBy.IsUnknown() { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created By")), - "", - ) - } else { - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Created By")), - info.CreatedBy, - ) - } - fmt.Println() - fmt.Println( - faint.Render(fmt.Sprintf("%-11s", "Value")), - string(secret), - ) -} - -const deleteSecretCmdUsage = `Usage: - kes secret rm [options] ... - -Options: - -k, --insecure Skip TLS certificate validation. - -e, --enclave Operate within the specified enclave. - - -h, --help Print command line options. - -Examples: - $ kes secret rm my-secret -` - -func deleteSecretCmd(args []string) { - cmd := flag.NewFlagSet(args[0], flag.ContinueOnError) - cmd.Usage = func() { fmt.Fprint(os.Stderr, deleteSecretCmdUsage) } - - var ( - insecureSkipVerify bool - enclaveName string - ) - cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation") - cmd.StringVarP(&enclaveName, "enclave", "e", "", "Operate within the specified enclave") - if err := cmd.Parse(args[1:]); err != nil { - if errors.Is(err, flag.ErrHelp) { - os.Exit(2) - } - cli.Fatalf("%v. See 'kes secret rm --help'", err) - } - if cmd.NArg() == 0 { - cli.Fatal("no secret name specified. See 'kes secret rm --help'") - } - - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) - defer cancel() - - enclave := newEnclave(enclaveName, insecureSkipVerify) - for _, name := range cmd.Args() { - if err := enclave.DeleteSecret(ctx, name); err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to remove secret %q: %v", name, err) - } - } -} - -const lsSecretCmdUsage = `Usage: - kes secret ls [options] [] - -Options: - -k, --insecure Skip TLS certificate validation. - --json Print keys in JSON format. - --color Specify when to use colored output. The automatic - mode only enables colors if an interactive terminal - is detected - colors are automatically disabled if - the output goes to a pipe. - Possible values: *auto*, never, always. - -e, --enclave Operate within the specified enclave. - - -h, --help Print command line options. - -Examples: - $ kes secret ls - $ kes secret ls 'my-secret*' -` - -func lsSecretCmd(args []string) { - cmd := flag.NewFlagSet(args[0], flag.ContinueOnError) - cmd.Usage = func() { fmt.Fprint(os.Stderr, lsSecretCmdUsage) } - - var ( - jsonFlag bool - colorFlag colorOption - insecureSkipVerify bool - enclaveName string - ) - cmd.BoolVar(&jsonFlag, "json", false, "Print identities in JSON format") - cmd.Var(&colorFlag, "color", "Specify when to use colored output") - cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation") - cmd.StringVarP(&enclaveName, "enclave", "e", "", "Operate within the specified enclave") - if err := cmd.Parse(args[1:]); err != nil { - if errors.Is(err, flag.ErrHelp) { - os.Exit(2) - } - cli.Fatalf("%v. See 'kes secret ls --help'", err) - } - - if cmd.NArg() > 1 { - cli.Fatal("too many arguments. See 'kes secret ls --help'") - } - - pattern := "*" - if cmd.NArg() == 1 { - pattern = cmd.Arg(0) - } - - ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) - defer cancelCtx() - - enclave := newEnclave(enclaveName, insecureSkipVerify) - iterator, err := enclave.ListSecrets(ctx, pattern) - if err != nil { - if errors.Is(err, context.Canceled) { - os.Exit(1) - } - cli.Fatalf("failed to list secrets: %v", err) - } - defer iterator.Close() - - if jsonFlag { - if _, err = iterator.WriteTo(os.Stdout); err != nil { - cli.Fatal(err) - } - if err = iterator.Close(); err != nil { - cli.Fatal(err) - } - } else { - secrets, err := iterator.Values(0) - if err != nil { - cli.Fatalf("failed to list keys: %v", err) - } - if err = iterator.Close(); err != nil { - cli.Fatalf("failed to list keys: %v", err) - } - - if len(secrets) > 0 { - sort.Slice(secrets, func(i, j int) bool { - return strings.Compare(secrets[i].Name, secrets[j].Name) < 0 - }) - - headerStyle := tui.NewStyle() - dateStyle := tui.NewStyle() - if colorFlag.Colorize() { - const ColorDate tui.Color = "#5f8700" - headerStyle = headerStyle.Underline(true).Bold(true) - dateStyle = dateStyle.Foreground(ColorDate) - } - - fmt.Println( - headerStyle.Render(fmt.Sprintf("%-19s", "Date Created")), - headerStyle.Render("Key"), - ) - for _, key := range secrets { - var date string - if key.CreatedAt.IsZero() { - date = fmt.Sprintf("%5s%s%5s", " ", "", " ") - } else { - year, month, day := key.CreatedAt.Local().Date() - hour, min, sec := key.CreatedAt.Local().Clock() - date = fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec) - } - fmt.Printf("%s %s\n", dateStyle.Render(date), key.Name) - } - } - - } -} diff --git a/cmd/kes/server.go b/cmd/kes/server.go index 229d532d..4618ce48 100644 --- a/cmd/kes/server.go +++ b/cmd/kes/server.go @@ -71,11 +71,7 @@ func serverCmd(args []string) { cli.Fatalf("%v. See 'kes server --help'", err) } - if cmd.NArg() == 0 { - cmd.Usage() - os.Exit(2) - } - if cmd.NArg() > 1 { + if cmd.NArg() > 0 { cli.Fatal("too many arguments. See 'kes server --help'") } diff --git a/go.mod b/go.mod index c50ddccb..79206ce9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/charmbracelet/lipgloss v0.7.1 github.com/fatih/color v1.13.0 github.com/hashicorp/vault/api v1.9.2 - github.com/minio/kes-go v0.1.0 + github.com/minio/kes-go v0.2.0 github.com/minio/selfupdate v0.4.0 github.com/muesli/termenv v0.15.2 github.com/prometheus/client_golang v1.16.0 diff --git a/go.sum b/go.sum index 8b8d002e..ab53a1c6 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/minio/kes-go v0.1.0 h1:h201DyOYP5sTqajkxFGxmXz/kPbT8HQNX1uh3Yx2PFc= -github.com/minio/kes-go v0.1.0/go.mod h1:VorHLaIYis9/MxAHAtXN4d8PUMNKhIxTIlvFt0hBOEo= +github.com/minio/kes-go v0.2.0 h1:HA33arq9s3MErbsj3PAXFVfFo4U4yw7lTKQ5kWFrpCA= +github.com/minio/kes-go v0.2.0/go.mod h1:VorHLaIYis9/MxAHAtXN4d8PUMNKhIxTIlvFt0hBOEo= github.com/minio/selfupdate v0.4.0 h1:A7t07pN4Ch1tBTIRStW0KhUVyykz+2muCqFsITQeEW8= github.com/minio/selfupdate v0.4.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= diff --git a/internal/api/identity.go b/internal/api/identity.go index 8b8cf069..84f07a1d 100644 --- a/internal/api/identity.go +++ b/internal/api/identity.go @@ -83,20 +83,15 @@ func edgeSelfDescribeIdentity(config *EdgeRouterConfig) API { Timeout = c.Timeout } } - type InlinePolicy struct { - Allow []string `json:"allow,omitempty"` - Deny []string `json:"deny,omitempty"` + type Response struct { + Identity kes.Identity `json:"identity"` + IsAdmin bool `json:"admin,omitempty"` CreatedAt time.Time `json:"created_at,omitempty"` CreatedBy kes.Identity `json:"created_by,omitempty"` - } - type Response struct { - Identity kes.Identity `json:"identity"` - IsAdmin bool `json:"admin,omitempty"` - PolicyName string `json:"policy_name,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - CreatedBy kes.Identity `json:"created_by,omitempty"` - Policy InlinePolicy `json:"policy"` + Policy string `json:"policy,omitempty"` + Allow map[string]kes.Rule `json:"allow,omitempty"` + Deny map[string]kes.Rule `json:"deny,omitempty"` } var handler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { identity := auth.Identify(r) @@ -115,17 +110,13 @@ func edgeSelfDescribeIdentity(config *EdgeRouterConfig) API { w.Header().Set("Content-Type", ContentType) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Identity: identity, - PolicyName: info.Policy, - IsAdmin: info.IsAdmin, - CreatedAt: info.CreatedAt, - CreatedBy: info.CreatedBy, - Policy: InlinePolicy{ - Allow: policy.Allow, - Deny: policy.Deny, - CreatedAt: policy.CreatedAt, - CreatedBy: policy.CreatedBy, - }, + Identity: identity, + IsAdmin: info.IsAdmin, + CreatedAt: info.CreatedAt, + CreatedBy: info.CreatedBy, + Policy: info.Policy, + Allow: policy.Allow, + Deny: policy.Deny, }) return nil } diff --git a/internal/api/key.go b/internal/api/key.go index 36b275ea..d49ec205 100644 --- a/internal/api/key.go +++ b/internal/api/key.go @@ -44,9 +44,9 @@ func edgeCreateKey(config *EdgeRouterConfig) API { var algorithm kes.KeyAlgorithm if fips.Enabled || cpu.HasAESGCM() { - algorithm = kes.AES256_GCM_SHA256 + algorithm = kes.AES256 } else { - algorithm = kes.XCHACHA20_POLY1305 + algorithm = kes.ChaCha20 } key, err := key.Random(algorithm, auth.Identify(r)) @@ -84,8 +84,8 @@ func edgeImportKey(config *EdgeRouterConfig) API { } } type Request struct { - Bytes []byte `json:"bytes"` - Algorithm kes.KeyAlgorithm `json:"algorithm"` + Bytes []byte `json:"key"` + Algorithm string `json:"cipher"` } var handler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { name, err := nameFromRequest(r, APIPath) @@ -100,10 +100,15 @@ func edgeImportKey(config *EdgeRouterConfig) API { if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return kes.NewError(http.StatusBadRequest, err.Error()) } - if len(req.Bytes) != key.Len(req.Algorithm) { + + var algorithm kes.KeyAlgorithm + if err := algorithm.UnmarshalText([]byte(req.Algorithm)); err != nil { + return kes.NewError(http.StatusBadRequest, err.Error()) + } + if len(req.Bytes) != key.Len(algorithm) { return kes.NewError(http.StatusBadRequest, "invalid key size") } - key, err := key.New(req.Algorithm, req.Bytes, auth.Identify(r)) + key, err := key.New(algorithm, req.Bytes, auth.Identify(r)) if err != nil { return err } diff --git a/internal/api/policy.go b/internal/api/policy.go index b3d36c82..4f8ce517 100644 --- a/internal/api/policy.go +++ b/internal/api/policy.go @@ -80,10 +80,10 @@ func edgeReadPolicy(config *EdgeRouterConfig) API { } } type Response struct { - Allow []string `json:"allow,omitempty"` - Deny []string `json:"deny,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - CreatedBy kes.Identity `json:"created_by,omitempty"` + Allow map[string]kes.Rule `json:"allow,omitempty"` + Deny map[string]kes.Rule `json:"deny,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + CreatedBy kes.Identity `json:"created_by,omitempty"` } var handler HandlerFunc = func(w http.ResponseWriter, r *http.Request) error { name, err := nameFromRequest(r, APIPath) diff --git a/internal/auth/policy.go b/internal/auth/policy.go index 2595f3b2..532dc354 100644 --- a/internal/auth/policy.go +++ b/internal/auth/policy.go @@ -5,10 +5,7 @@ package auth import ( - "bytes" "context" - "encoding" - "encoding/gob" "net/http" "path" "time" @@ -79,11 +76,11 @@ type PolicyIterator interface { type Policy struct { // Allow is a list of glob patterns that are matched // against the URL path of incoming requests. - Allow []string + Allow map[string]kes.Rule // Deny is a list of glob patterns that are matched // against the URL path of incoming requests. - Deny []string + Deny map[string]kes.Rule // CreatedAt is the point in time when the policy // has been created. @@ -93,47 +90,6 @@ type Policy struct { CreatedBy kes.Identity } -var ( - _ encoding.BinaryMarshaler = Policy{} - _ encoding.BinaryUnmarshaler = (*Policy)(nil) -) - -// MarshalBinary returns the Policy's binary representation. -func (p Policy) MarshalBinary() ([]byte, error) { - type GOB struct { - Allow []string - Deny []string - CreatedAt time.Time - CreatedBy kes.Identity - } - - var buffer bytes.Buffer - if err := gob.NewEncoder(&buffer).Encode(GOB(p)); err != nil { - return nil, err - } - return buffer.Bytes(), nil -} - -// UnmarshalBinary unmarshals the Policy's binary representation. -func (p *Policy) UnmarshalBinary(b []byte) error { - type GOB struct { - Allow []string - Deny []string - CreatedAt time.Time - CreatedBy kes.Identity - } - - var value GOB - if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&value); err != nil { - return err - } - p.Allow = value.Allow - p.Deny = value.Deny - p.CreatedAt = value.CreatedAt - p.CreatedBy = value.CreatedBy - return nil -} - // Verify reports whether the given HTTP request is allowed. // It returns no error if: // @@ -142,12 +98,12 @@ func (p *Policy) UnmarshalBinary(b []byte) error { // // Otherwise, Verify returns ErrNotAllowed. func (p *Policy) Verify(r *http.Request) error { - for _, pattern := range p.Deny { + for pattern := range p.Deny { if ok, err := path.Match(pattern, r.URL.Path); ok && err == nil { return kes.ErrNotAllowed } } - for _, pattern := range p.Allow { + for pattern := range p.Allow { if ok, err := path.Match(pattern, r.URL.Path); ok && err == nil { return nil } diff --git a/internal/key/ciphertext.go b/internal/key/ciphertext.go index 60f7733c..0b1a39d5 100644 --- a/internal/key/ciphertext.go +++ b/internal/key/ciphertext.go @@ -160,9 +160,9 @@ func (c *ciphertext) UnmarshalJSON(text []byte) error { } if value.Algorithm == AES256GCM { - c.Algorithm = kes.AES256_GCM_SHA256 + c.Algorithm = kes.AES256 } else { - c.Algorithm = kes.XCHACHA20_POLY1305 + c.Algorithm = kes.ChaCha20 } c.ID = value.ID c.IV = value.IV diff --git a/internal/key/key.go b/internal/key/key.go index 7e68b129..351dab3f 100644 --- a/internal/key/key.go +++ b/internal/key/key.go @@ -17,10 +17,10 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "time" "github.com/minio/kes-go" - "github.com/minio/kes/internal/cpu" "github.com/minio/kes/internal/fips" "golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20poly1305" @@ -46,13 +46,12 @@ func Parse(b []byte) (Key, error) { // Len returns the length of keys for the given Algorithm in bytes. func Len(a kes.KeyAlgorithm) int { switch a { - case kes.AES256_GCM_SHA256: + case kes.AES256: return 256 / 8 - case kes.XCHACHA20_POLY1305: + case kes.ChaCha20: return 256 / 8 - case kes.KeyAlgorithmUndefined: - return 256 / 8 // For generic/unknown keys, return 256 bit. default: + fmt.Println(int(a)) return -1 } } @@ -235,13 +234,6 @@ func (k *Key) Wrap(plaintext, associatedData []byte) ([]byte, error) { } algorithm := k.Algorithm() - if algorithm == kes.KeyAlgorithmUndefined { - if fips.Enabled || cpu.HasAESGCM() { - algorithm = kes.AES256_GCM_SHA256 - } else { - algorithm = kes.XCHACHA20_POLY1305 - } - } cipher, err := newAEAD(algorithm, k.bytes, iv) if err != nil { return nil, err @@ -272,13 +264,6 @@ func (k *Key) Unwrap(ciphertext, associatedData []byte) ([]byte, error) { return nil, kes.ErrDecrypt } - if text.ID != "" && text.ID != k.ID() { // Ciphertexts generated in the past may not contain a key ID - return nil, kes.ErrDecrypt - } - if k.algorithm != kes.KeyAlgorithmUndefined && text.Algorithm != k.Algorithm() { - return nil, kes.ErrDecrypt - } - cipher, err := newAEAD(text.Algorithm, k.bytes, text.IV) if err != nil { return nil, kes.ErrDecrypt @@ -294,7 +279,7 @@ func (k *Key) Unwrap(ciphertext, associatedData []byte) ([]byte, error) { // algorithm and is initialized with the given key and iv. func newAEAD(algorithm kes.KeyAlgorithm, Key, IV []byte) (cipher.AEAD, error) { switch algorithm { - case kes.AES256_GCM_SHA256: + case kes.AES256: mac := hmac.New(sha256.New, Key) mac.Write(IV) sealingKey := mac.Sum(nil) @@ -304,7 +289,7 @@ func newAEAD(algorithm kes.KeyAlgorithm, Key, IV []byte) (cipher.AEAD, error) { return nil, err } return cipher.NewGCM(block) - case kes.XCHACHA20_POLY1305: + case kes.ChaCha20: if fips.Enabled { return nil, kes.ErrDecrypt } diff --git a/internal/key/key_test.go b/internal/key/key_test.go index 8675ced7..23f55f95 100644 --- a/internal/key/key_test.go +++ b/internal/key/key_test.go @@ -31,14 +31,14 @@ var parseTests = []struct { { Raw: `{"bytes":"J8qmOyEV2ce2yoAC+5t0Y7CSP/hTMppL7XHpAnyc+0E=","algorithm":"AES256-GCM_SHA256","created_at":"2009-11-10T23:00:00Z","created_by":"40235905b7b83e0537a002db523cd019d6709b899adc249c957860cd00fa9f78"}`, Bytes: mustDecodeHex("27caa63b2115d9c7b6ca8002fb9b7463b0923ff853329a4bed71e9027c9cfb41"), - Algorithm: kes.AES256_GCM_SHA256, + Algorithm: kes.AES256, CreatedAt: mustDecodeTime("2009-11-10T23:00:00Z"), CreatedBy: "40235905b7b83e0537a002db523cd019d6709b899adc249c957860cd00fa9f78", }, { Raw: `{"bytes":"9ew6BCae3+13sniOUwttEJ62amg98YXc0OW0WBhNiCY=","algorithm":"XCHACHA20-POLY1305","created_at":"2009-11-10T23:00:00Z","created_by":"189d9de5331e3ee8abe9e4bd40d474ad621d79ccf83a711f6ac68050eb15a52a"}`, Bytes: mustDecodeHex("f5ec3a04269edfed77b2788e530b6d109eb66a683df185dcd0e5b458184d8826"), - Algorithm: kes.XCHACHA20_POLY1305, + Algorithm: kes.ChaCha20, CreatedAt: mustDecodeTime("2009-11-10T23:00:00Z"), CreatedBy: "189d9de5331e3ee8abe9e4bd40d474ad621d79ccf83a711f6ac68050eb15a52a", }, @@ -89,7 +89,7 @@ var keyWrapTests = []struct { } func TestKeyWrap(t *testing.T) { - algorithms := []kes.KeyAlgorithm{kes.AES256_GCM_SHA256, kes.XCHACHA20_POLY1305} + algorithms := []kes.KeyAlgorithm{kes.AES256, kes.ChaCha20} for _, a := range algorithms { key, err := Random(a, "") if err != nil { @@ -124,87 +124,80 @@ var keyUnwrapTests = []struct { Err error }{ { // 0 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.AES256, Ciphertext: `{"aead":"AES-256-GCM-HMAC-SHA-256","iv":"xLxIN3tSCkg2xMafuvwUwg==","nonce":"gu0mGwUkwcvMEoi5","bytes":"WVgRjeIJm3w50C/l+y7y2i6mbNg5NCAqN1zvOYWZKmc="}`, AssociatedData: nil, }, { // 1 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.ChaCha20, Ciphertext: `{"aead":"ChaCha20Poly1305","iv":"s3fSZ6vk5m+DfQA8yZWeUg==","nonce":"8/kHMnCMs3h9NZ2a","bytes":"cw22HjLq/4cx8507SW4hhSrYbDiMuRao4b5+GE+XfbE="}`, AssociatedData: nil, }, { // 2 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.ChaCha20, Ciphertext: `{"aead":"ChaCha20Poly1305","id":"66687aadf862bd776c8fc18b8e9f8e20","iv":"EC0eZp7Pqt+LnkOae5xaAg==","nonce":"X1ejXKmH/ugFZPkk","bytes":"wIGBTDs6aOvsqJfekZ0PYRT/OHyFX2TXqeNwl1SLXOI="}`, AssociatedData: nil, }, { // 3 - Algorithm: kes.AES256_GCM_SHA256, + Algorithm: kes.AES256, Ciphertext: string(mustDecodeB64("lbFBRVMyNTYtR0NNX1NIQTI1NtkgNjY2ODdhYWRmODYyYmQ3NzZjOGZjMThiOGU5ZjhlMjDEEExv7LAd4oz0SaHZrX5LBufEDEKME1ow1CDfUFrqv8QgJuy7Sw+jVqz99TK1HV851LT3K4mwwDv46TB2ngWkAJQ=")), AssociatedData: nil, }, { // 4 - Algorithm: kes.XCHACHA20_POLY1305, + Algorithm: kes.ChaCha20, Ciphertext: string(mustDecodeB64("lbJYQ0hBQ0hBMjAtUE9MWTEzMDXZIDY2Njg3YWFkZjg2MmJkNzc2YzhmYzE4YjhlOWY4ZTIwxBBAr+aptD4x2+qfOhiErbnkxAxYs8RmNC1JJXD1hiHEIJ2KqM0jjkME7ndx8nyVseesN83Np0rM5ejVUun+fNFu")), AssociatedData: nil, }, { // 5 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.AES256, Ciphertext: `{"aead":"AES-256-GCM","iv":"xLxIN3tSCkg2xMafuvwUwg==","nonce":"gu0mGwUkwcvMEoi5","bytes":"WVgRjeIJm3w50C/l+y7y2i6mbNg5NCAqN1zvOYWZKmc="}`, AssociatedData: nil, ShouldFail: true, // Invalid algorithm Err: kes.ErrDecrypt, }, { // 6 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.AES256, Ciphertext: `{"aead":"AES-256-GCM-HMAC-SHA-256","iv":"EjOY4JKqjIrPmQ5z1KSR8zlhggY=","nonce":"gu0mGwUkwcvMEoi5","bytes":"WVgRjeIJm3w50C/l+y7y2i6mbNg5NCAqN1zvOYWZKmc="}`, AssociatedData: nil, ShouldFail: true, // invalid IV length Err: kes.ErrDecrypt, }, { // 7 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.ChaCha20, Ciphertext: `{"aead":"ChaCha20Poly1305","iv":"s3fSZ6vk5m+DfQA8yZWeUg==","nonce":"SXAbms731/c=","bytes":"cw22HjLq/4cx8507SW4hhSrYbDiMuRao4b5+GE+XfbE="}`, AssociatedData: nil, ShouldFail: true, // invalid nonce length Err: kes.ErrDecrypt, }, { // 8 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.AES256, Ciphertext: `{"aead":"AES-256-GCM-HMAC-SHA-256","iv":"xLxIN3tSCkg2xMafuvwUwg==","nonce":"efY+4kYF9n8=","bytes":"WVgRjeIJm3w50C/l+y7y2i6mbNg5NCAqN1zvOYWZKmc="}`, AssociatedData: nil, ShouldFail: true, // invalid nonce length Err: kes.ErrDecrypt, }, { // 9 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.AES256, Ciphertext: `{"aead":"AES-256-GCM-HMAC-SHA-256","iv":"xLxIN3tSCkg2xMafuvwUwg==","nonce":"gu0mGwUkwcvMEoi5","bytes":"QTza1g5oX3f9cGJMbY1xJwWPj1F7R2VnNl6XpFKYQy0="}`, AssociatedData: nil, ShouldFail: true, // ciphertext not authentic Err: kes.ErrDecrypt, }, { // 10 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.ChaCha20, Ciphertext: `{"aead":"ChaCha20Poly1305","iv":"s3fSZ6vk5m+DfQA8yZWeUg==","nonce":"8/kHMnCMs3h9NZ2a","bytes":"TTi8pkO+Jh1JWAHvPxZeUk/iVoBPUCE4ZSVGBy3fW2s="}`, AssociatedData: nil, ShouldFail: true, // ciphertext not authentic Err: kes.ErrDecrypt, }, { // 11 - Algorithm: kes.KeyAlgorithmUndefined, + Algorithm: kes.AES256, Ciphertext: `{"aead":"AES-256-GCM-HMAC-SHA-256" "iv":"xLxIN3tSCkg2xMafuvwUwg==","nonce":"gu0mGwUkwcvMEoi5","bytes":"WVgRjeIJm3w50C/l+y7y2i6mbNg5NCAqN1zvOYWZKmc="}`, AssociatedData: nil, ShouldFail: true, // invalid JSON Err: kes.ErrDecrypt, }, - { // 12 - Algorithm: kes.KeyAlgorithmUndefined, - Ciphertext: `{"aead":"AES-256-GCM-HMAC-SHA-256", "id":"00010203040506070809101112131415", "iv":"xLxIN3tSCkg2xMafuvwUwg==","nonce":"gu0mGwUkwcvMEoi5","bytes":"WVgRjeIJm3w50C/l+y7y2i6mbNg5NCAqN1zvOYWZKmc="}`, - AssociatedData: nil, - ShouldFail: true, // invalid key ID - Err: kes.ErrDecrypt, - }, } func TestKeyUnwrap(t *testing.T) { diff --git a/internal/keystore/kes/kes.go b/internal/keystore/kes/kes.go index 2b683b96..24e3969c 100644 --- a/internal/keystore/kes/kes.go +++ b/internal/keystore/kes/kes.go @@ -9,6 +9,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "io" "time" "github.com/minio/kes-go" @@ -154,26 +155,36 @@ func (s *Store) Delete(ctx context.Context, name string) error { // List returns a new kms.Iter over all stored entries. func (s *Store) List(ctx context.Context) (kv.Iter[string], error) { - enclave := s.client.Enclave(s.enclave) - i, err := enclave.ListSecrets(ctx, "*") - if err != nil { - return nil, err - } - return &iter{i}, nil + return &iter{ + ctx: ctx, + iter: &kes.ListIter[string]{ + NextFunc: s.client.Enclave(s.enclave).ListSecrets, + }, + }, nil } // Close closes the Store. func (s *Store) Close() error { return nil } type iter struct { - *kes.SecretIter + ctx context.Context + iter *kes.ListIter[string] + err error } func (i *iter) Next() (string, bool) { - if i.SecretIter.Next() { - return i.Name(), true + if i.err != nil { + return "", false } - return "", false + + next, err := i.iter.Next(i.ctx) + i.err = err + return next, err == nil } -func (i *iter) Close() error { return i.SecretIter.Close() } +func (i *iter) Close() error { + if i.err == io.EOF { + return nil + } + return i.err +} diff --git a/kestest/gateway_aws_test.go b/kestest/gateway_aws_test.go index 2556a5fe..1906dcdb 100644 --- a/kestest/gateway_aws_test.go +++ b/kestest/gateway_aws_test.go @@ -45,7 +45,6 @@ func TestGatewayAWS(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_azure_test.go b/kestest/gateway_azure_test.go index 3d4667d5..f1805043 100644 --- a/kestest/gateway_azure_test.go +++ b/kestest/gateway_azure_test.go @@ -44,7 +44,6 @@ func TestGatewayAzure(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_entrust_test.go b/kestest/gateway_entrust_test.go index 33c6ff7b..a2c43391 100644 --- a/kestest/gateway_entrust_test.go +++ b/kestest/gateway_entrust_test.go @@ -44,7 +44,6 @@ func TestGatewayEntrust(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_fortanix_test.go b/kestest/gateway_fortanix_test.go index a6650913..e4197af0 100644 --- a/kestest/gateway_fortanix_test.go +++ b/kestest/gateway_fortanix_test.go @@ -44,7 +44,6 @@ func TestGatewayFortanix(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_fs_test.go b/kestest/gateway_fs_test.go index e9250546..68b9c6a0 100644 --- a/kestest/gateway_fs_test.go +++ b/kestest/gateway_fs_test.go @@ -33,7 +33,6 @@ func TestGatewayFS(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_gcp_test.go b/kestest/gateway_gcp_test.go index 8b3a7eab..1a1201a1 100644 --- a/kestest/gateway_gcp_test.go +++ b/kestest/gateway_gcp_test.go @@ -45,7 +45,6 @@ func TestGatewayGCP(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_gemalto_test.go b/kestest/gateway_gemalto_test.go index a0c0b9c6..e45152a1 100644 --- a/kestest/gateway_gemalto_test.go +++ b/kestest/gateway_gemalto_test.go @@ -44,7 +44,6 @@ func TestGatewayGemalto(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_mem_test.go b/kestest/gateway_mem_test.go index fbe36760..1ef7c7ca 100644 --- a/kestest/gateway_mem_test.go +++ b/kestest/gateway_mem_test.go @@ -24,7 +24,6 @@ func TestGatewayMem(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/gateway_test.go b/kestest/gateway_test.go index e81e1a04..11f48814 100644 --- a/kestest/gateway_test.go +++ b/kestest/gateway_test.go @@ -12,9 +12,10 @@ import ( "encoding/base64" "errors" "fmt" + "io" + "maps" "math/rand" "net/http" - "sort" "strconv" "testing" "time" @@ -134,7 +135,7 @@ func testCreateKey(ctx context.Context, store kv.Store[string, []byte], t *testi defer server.Close() client := server.Client() - defer clean(ctx, client, t, seed) + defer clean(ctx, client, t) for i, test := range createKeyTests { err := client.CreateKey(ctx, fmt.Sprintf("%s-%s", test.Name, seed)) @@ -184,10 +185,10 @@ func testImportKey(ctx context.Context, store kv.Store[string, []byte], t *testi defer server.Close() client := server.Client() - defer clean(ctx, client, t, seed) + defer clean(ctx, client, t) for i, test := range importKeyTests { - err := client.ImportKey(ctx, fmt.Sprintf("%s-%s", test.Name, seed), test.Key) + err := client.ImportKey(ctx, fmt.Sprintf("%s-%s", test.Name, seed), &kes.ImportKeyRequest{Key: test.Key}) if err == nil && test.ShouldFail { t.Fatalf("Test %d: should fail but succeeded", i) } @@ -217,7 +218,7 @@ func testGenerateKey(ctx context.Context, store kv.Store[string, []byte], t *tes defer server.Close() client := server.Client() - defer clean(ctx, client, t, seed) + defer clean(ctx, client, t) if err := client.CreateKey(ctx, keyName); err != nil { t.Fatalf("Failed to create %q: %v", keyName, err) @@ -266,7 +267,7 @@ func testEncryptKey(ctx context.Context, store kv.Store[string, []byte], t *test defer server.Close() client := server.Client() - defer clean(ctx, client, t, seed) + defer clean(ctx, client, t) if err := client.CreateKey(ctx, keyName); err != nil { t.Fatalf("Failed to create %q: %v", keyName, err) @@ -324,10 +325,10 @@ func testDecryptKey(ctx context.Context, store kv.Store[string, []byte], t *test defer server.Close() client := server.Client() - defer clean(ctx, client, t, seed) + defer clean(ctx, client, t) const KeyValue = "pQLPe6/f87AMSItvZzEbrxYdRUzmM81ziXF95HOFE4Y=" - if err := client.ImportKey(ctx, keyName, mustDecodeB64(KeyValue)); err != nil { + if err := client.ImportKey(ctx, keyName, &kes.ImportKeyRequest{Key: mustDecodeB64(KeyValue)}); err != nil { t.Fatalf("Failed to create %q: %v", keyName, err) } for i, test := range decryptKeyTests { @@ -347,88 +348,6 @@ func testDecryptKey(ctx context.Context, store kv.Store[string, []byte], t *test } } -var decryptAllKeyTests = []struct { - Ciphertexts []kes.CCP - Plaintexts []kes.PCP - ShouldFail bool -}{ - { // 0 - Ciphertexts: []kes.CCP{ - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiQkpDU2FRZ1MrMUovZ3ZhcWZNaXJYUT09Iiwibm9uY2UiOiJHZkllRHdSdjByRDBIYncrIiwiYnl0ZXMiOiIvNndhelRQbnREMHhra0w5RWFGWjduK0s5SEJhem5YaDlKYjcifQ==")}, - }, - Plaintexts: []kes.PCP{ - {Plaintext: []byte("Hello World")}, - }, - }, - { // 1 - Ciphertexts: []kes.CCP{ - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiQkpDU2FRZ1MrMUovZ3ZhcWZNaXJYUT09Iiwibm9uY2UiOiJHZkllRHdSdjByRDBIYncrIiwiYnl0ZXMiOiIvNndhelRQbnREMHhra0w5RWFGWjduK0s5SEJhem5YaDlKYjcifQ==")}, - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiR3pFcFI0am1JMWRWTzJsdXZvdG9xQT09Iiwibm9uY2UiOiJCV2c1eE54eU4yck9sLzV3IiwiYnl0ZXMiOiJmVXlycTI1Q3VDeEp4TW5XOXVZSSsrSjVsVzdGbVFtcmZpdEoifQ=="), Context: []byte("Hello World Context")}, - }, - Plaintexts: []kes.PCP{ - {Plaintext: []byte("Hello World")}, - {Plaintext: []byte("Hello World"), Context: []byte("Hello World Context")}, - }, - }, - { // 2 - Ciphertexts: []kes.CCP{ - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiQkpDU2FRZ1MrMUovZ3ZhcWZNaXJYUT09Iiwibm9uY2UiOiJHZkllRHdSdjByRDBIYncrIiwiYnl0ZXMiOiIvNndhelRQbnREMHhra0w5RWFGWjduK0s5SEJhem5YaDlKYjcifQ==")}, - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiR3pFcFI0am1JMWRWTzJsdXZvdG9xQT09Iiwibm9uY2UiOiJCV2c1eE54eU4yck9sLzV3IiwiYnl0ZXMiOiJmVXlycTI1Q3VDeEp4TW5XOXVZSSsrSjVsVzdGbVFtcmZpdEoifQ=="), Context: []byte("Hello World Context")}, - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiRDc5M3VKOEtuUjlrUjBzUm9QNGt5Zz09Iiwibm9uY2UiOiJOQ245dkFqQUhla0QyQW9OIiwiYnl0ZXMiOiJrZGZVRjErMVIvSEFXRkhrU3RjRGdkOHlya3hSUmYvNFV4ZmtPTGxiWjZJM0IxWml3MG0yUjZkM2JZalE3NVZ6In0="), Context: mustDecodeB64("3L+XLd07zRgH+JT/TDGj5Q==")}, - }, - Plaintexts: []kes.PCP{ - {Plaintext: []byte("Hello World")}, - {Plaintext: []byte("Hello World"), Context: []byte("Hello World Context")}, - {Plaintext: mustDecodeB64("20p8/WDxkN2ekJWmOpabC48urRMnhfbAUOUB6TvRAN8="), Context: mustDecodeB64("3L+XLd07zRgH+JT/TDGj5Q==")}, - }, - }, - - { // 3 - Ciphertexts: []kes.CCP{ - {Ciphertext: mustDecodeB64("eyJhZWFkIjoiQUVTLTI1Ni1HQ00tSE1BQy1TSEEtMjU2IiwiaWQiOiI2MmNmMjEzMDY2OTI3MmYzOWY3ZGU2MDU4Y2YzNzEyMyIsIml2IjoiR3pFcFI0am1JMWRWTzJsdXZvdG9xQT09Iiwibm9uY2UiOiJCV2c1eE54eU4yck9sLzV3IiwiYnl0ZXMiOiJmVXlycTI1Q3VDeEp4TW5XOXVZSSsrSjVsVzdGbVFtcmZpdEoifQ==")}, - }, - ShouldFail: true, // Wrong context - }, -} - -func testDecryptKeyAll(ctx context.Context, store kv.Store[string, []byte], t *testing.T, seed string) { - keyName := fmt.Sprintf("kestest-%s", seed) - server := kestest.NewGateway(store) - defer server.Close() - client := server.Client() - - defer clean(ctx, client, t, seed) - - const KeyValue = "pQLPe6/f87AMSItvZzEbrxYdRUzmM81ziXF95HOFE4Y=" - if err := client.ImportKey(ctx, keyName, mustDecodeB64(KeyValue)); err != nil { - t.Fatalf("Failed to create %q: %v", keyName, err) - } - - for i, test := range decryptAllKeyTests { - plaintexts, err := client.DecryptAll(ctx, keyName, test.Ciphertexts...) - if test.ShouldFail { - if err == nil { - t.Fatalf("Test %d: should fail but succeeded", i) - } - continue - } - if err != nil { - t.Fatalf("Test %d: failed to decrypt ciphertexts: %v", i, err) - } - if len(plaintexts) != len(test.Plaintexts) { - t.Fatalf("Test %d: plaintext mismatch: got len '%d' - want len '%d'", i, len(plaintexts), len(test.Plaintexts)) - } - for j := range test.Plaintexts { - if !bytes.Equal(plaintexts[j].Plaintext, test.Plaintexts[j].Plaintext) { - t.Fatalf("Test %d: %d-nth plaintext mismatch: got '%x' - want '%x'", i, j, plaintexts[j].Plaintext, test.Plaintexts[j].Plaintext) - } - if !bytes.Equal(plaintexts[j].Context, test.Plaintexts[j].Context) { - t.Fatalf("Test %d: %d-nth context mismatch: got '%x' - want '%x'", i, j, plaintexts[j].Context, test.Plaintexts[j].Context) - } - } - } -} - var getPolicyTests = []struct { Name string Policy *kes.Policy @@ -437,14 +356,14 @@ var getPolicyTests = []struct { { Name: "my-policy2", Policy: &kes.Policy{ - Allow: []string{"/v1/key/create/*", "/v1/key/generate/*"}, + Allow: map[string]kes.Rule{"/v1/key/create/*": {}, "/v1/key/generate/*": {}}, }, }, { Name: "my-policy2", Policy: &kes.Policy{ - Allow: []string{"/v1/key/create/*", "/v1/key/generate/*"}, - Deny: []string{"/v1/key/create/my-key2"}, + Allow: map[string]kes.Rule{"/v1/key/create/*": {}, "/v1/key/generate/*": {}}, + Deny: map[string]kes.Rule{"/v1/key/create/my-key2": {}}, }, }, } @@ -488,38 +407,18 @@ func testGetPolicy(ctx context.Context, store kv.Store[string, []byte], t *testi if err != nil { t.Fatalf("Test %d: failed to describe policy: %v", i, err) } - if policy.Info.Name != test.Name { - t.Fatalf("Policy name mismatch: got '%s' - want '%s'", policy.Info.Name, test.Name) - } - if policy.Info.Name != test.Name { - t.Fatalf("Test %d: policy name mismatch: got '%s' - want '%s'", i, policy.Info.Name, test.Name) - } - if policy.Info.CreatedAt.IsZero() { + if policy.CreatedAt.IsZero() { t.Fatalf("Test %d: created_at timestamp not set", i) } - if policy.Info.CreatedBy.IsUnknown() { + if policy.CreatedBy.IsUnknown() { t.Fatalf("Test %d: created_by identity not set", i) } - if len(policy.Allow) != len(test.Policy.Allow) { - t.Fatalf("Test %d: allow policy mismatch: got len %d - want len %d", i, len(policy.Allow), len(test.Policy.Allow)) - } - sort.Strings(test.Policy.Allow) - sort.Strings(policy.Allow) - for j := range policy.Allow { - if policy.Allow[j] != test.Policy.Allow[j] { - t.Fatalf("Test %d: allow policy mismatch: got '%s' - want '%s'", i, policy.Allow[j], test.Policy.Allow[j]) - } + if !maps.Equal(policy.Allow, test.Policy.Allow) { + t.Fatalf("Test %d: allow policy mismatch: got '%v' - want '%v'", i, policy.Allow, test.Policy.Allow) } - if len(policy.Deny) != len(test.Policy.Deny) { - t.Fatalf("Test %d: deny policy mismatch: got len %d - want len %d", i, len(policy.Deny), len(test.Policy.Deny)) - } - sort.Strings(test.Policy.Deny) - sort.Strings(policy.Deny) - for j := range policy.Deny { - if policy.Deny[j] != test.Policy.Deny[j] { - t.Fatalf("Test %d: deny policy mismatch: got '%s' - want '%s'", i, policy.Deny[j], test.Policy.Deny[j]) - } + if !maps.Equal(policy.Deny, test.Policy.Deny) { + t.Fatalf("Test %d: deny policy mismatch: got '%v' - want '%v'", i, policy.Deny, test.Policy.Deny) } }) } @@ -532,18 +431,18 @@ var selfDescribeTests = []struct { Policy: kes.Policy{}, }, { // 1 - Policy: kes.Policy{Allow: []string{}, Deny: []string{}}, + Policy: kes.Policy{Allow: map[string]kes.Rule{}, Deny: map[string]kes.Rule{}}, }, { // 2 Policy: kes.Policy{ - Allow: []string{ - "/v1/key/create/my-key-*", - "/v1/key/generate/my-key-*", - "/v1/key/decrypt/my-key-*", - "/v1/key/delete/my-key-*", + Allow: map[string]kes.Rule{ + "/v1/key/create/my-key-*": {}, + "/v1/key/generate/my-key-*": {}, + "/v1/key/decrypt/my-key-*": {}, + "/v1/key/delete/my-key-*": {}, }, - Deny: []string{ - "/v1/key/delete/my-key-prod-*", + Deny: map[string]kes.Rule{ + "/v1/key/delete/my-key-prod-*": {}, }, }, }, @@ -591,10 +490,10 @@ func testSelfDescribe(ctx context.Context, store kv.Store[string, []byte], t *te if id := kestest.Identify(&cert); info.Identity != id { t.Fatalf("Test %d: identity mismatch: got '%v' - want '%v'", i, info.Identity, id) } - if !equal(policy.Allow, test.Policy.Allow) { + if !maps.Equal(policy.Allow, test.Policy.Allow) { t.Fatalf("Test %d: allow policy mismatch: got '%v' - want '%v'", i, policy.Allow, test.Policy.Allow) } - if !equal(policy.Deny, test.Policy.Deny) { + if !maps.Equal(policy.Deny, test.Policy.Deny) { t.Fatalf("Test %d: deny policy mismatch: got '%v' - want '%v'", i, policy.Deny, test.Policy.Deny) } } @@ -608,21 +507,6 @@ func testingContext(t *testing.T) (context.Context, context.CancelFunc) { return context.WithCancel(context.Background()) } -func equal(a, b []string) bool { - if len(a) != len(b) { - return false - } - - sort.Strings(a) - sort.Strings(b) - for i := range a { - if a[i] != b[i] { - return false - } - } - return true -} - func mustDecodeB64(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { @@ -631,20 +515,22 @@ func mustDecodeB64(s string) []byte { return b } -func clean(ctx context.Context, client *kes.Client, t *testing.T, seed string) { - iter, err := client.ListKeys(ctx, fmt.Sprintf("kestest-%s*", seed)) - if err != nil { - t.Fatalf("Cleanup: failed to list keys: %v", err) +func clean(ctx context.Context, client *kes.Client, t *testing.T) { + iter := &kes.ListIter[string]{ + NextFunc: client.ListKeys, } - defer iter.Close() - keysInfo, err := iter.Values(-1) - if err != nil { - t.Fatalf("Cleanup: failed to iterate keys") + var names []string + for name, err := iter.SeekTo(ctx, "*"); err != io.EOF; name, err = iter.Next(ctx) { + if err != nil { + t.Fatal(err) + } + names = append(names, name) } - for _, info := range keysInfo { - if err = client.DeleteKey(ctx, info.Name); err != nil && !errors.Is(err, kes.ErrKeyNotFound) { - t.Errorf("Cleanup: failed to delete '%s': %v", info.Name, err) + + for _, name := range names { + if err := client.DeleteKey(ctx, name); err != nil && !errors.Is(err, kes.ErrKeyNotFound) { + t.Errorf("Cleanup: failed to delete '%s': %v", name, err) } } } diff --git a/kestest/gateway_vault_test.go b/kestest/gateway_vault_test.go index 6ac41216..efcecd41 100644 --- a/kestest/gateway_vault_test.go +++ b/kestest/gateway_vault_test.go @@ -44,7 +44,6 @@ func TestGatewayVault(t *testing.T) { t.Run("GenerateKey", func(t *testing.T) { testGenerateKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("EncryptKey", func(t *testing.T) { testEncryptKey(ctx, store, t, RandString(ranStringLength)) }) t.Run("DecryptKey", func(t *testing.T) { testDecryptKey(ctx, store, t, RandString(ranStringLength)) }) - t.Run("DecryptKeyAll", func(t *testing.T) { testDecryptKeyAll(ctx, store, t, RandString(ranStringLength)) }) t.Run("DescribePolicy", func(t *testing.T) { testDescribePolicy(ctx, store, t) }) t.Run("GetPolicy", func(t *testing.T) { testGetPolicy(ctx, store, t) }) t.Run("SelfDescribe", func(t *testing.T) { testSelfDescribe(ctx, store, t) }) diff --git a/kestest/policy.go b/kestest/policy.go index da4f3a44..03c9dddc 100644 --- a/kestest/policy.go +++ b/kestest/policy.go @@ -48,7 +48,11 @@ func (p *PolicySet) Add(name string, policy *kes.Policy) { // Allow is a shorthand for first creating a KES Policy // and then adding it to the PolicySet. func (p *PolicySet) Allow(name string, patterns ...string) { - p.Add(name, &kes.Policy{Allow: patterns}) + allow := make(map[string]kes.Rule, len(patterns)) + for _, pattern := range patterns { + allow[pattern] = kes.Rule{} + } + p.Add(name, &kes.Policy{Allow: allow}) } // Assign assigns the KES policy with the given name to