Skip to content
This repository was archived by the owner on Jun 19, 2025. It is now read-only.

Commit ef5d2f8

Browse files
committed
cmd: add kes ls command
This commit adds the `kes ls` command that lists keys, policies and identities. ``` Usage: kes ls [-a KEY] [-k] [--json] [-i] [-p] [-s HOST[:PORT]] [PREFIX] Options: -a, --api-key KEY API key to authenticate to the KES server. Defaults to $MINIO_KES_API_KEY. -s, --server HOST[:PORT] Use the server HOST[:PORT] instead of $MINIO_KES_SERVER. --json Print output in JSON format. -i, --identity List identities. -p, --policy List policy names. -k, --insecure Skip server certificate verification. ``` This command replaces `kes key ls`, `kes policy ls` and `kes identity ls` mid-term. Signed-off-by: Andreas Auernhammer <github@aead.dev>
1 parent 802ce81 commit ef5d2f8

File tree

11 files changed

+426
-107
lines changed

11 files changed

+426
-107
lines changed

cmd/kes/color-option.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ package main
66

77
import (
88
"errors"
9-
"os"
109
"strings"
1110

1211
tui "github.com/charmbracelet/lipgloss"
12+
"github.com/minio/kes/internal/cli"
1313
"github.com/muesli/termenv"
1414
flag "github.com/spf13/pflag"
1515
)
@@ -29,7 +29,7 @@ var _ flag.Value = (*colorOption)(nil)
2929

3030
func (c *colorOption) Colorize() bool {
3131
v := strings.ToLower(c.value)
32-
return v == "always" || ((v == "auto" || v == "") && isTerm(os.Stdout))
32+
return v == "always" || ((v == "auto" || v == "") && cli.IsTerminal())
3333
}
3434

3535
func (c *colorOption) String() string { return c.value }

cmd/kes/flags.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package main
2+
3+
import (
4+
"github.com/minio/kes/internal/cli"
5+
flag "github.com/spf13/pflag"
6+
)
7+
8+
// Use register functions for common flags exposed by
9+
// many commands. Common flags should have common names
10+
// to make command usage consistent.
11+
12+
// flagsInsecureSkipVerify adds a bool flag '-k, --insecure'
13+
// that sets insecureSkipVerify to true if provided on the
14+
// command line.
15+
func flagsInsecureSkipVerify(f *flag.FlagSet, insecureSkipVerify *bool) {
16+
f.BoolVarP(insecureSkipVerify, "insecure", "k", false, "Skip server certificate verification")
17+
}
18+
19+
func flagsAPIKey(f *flag.FlagSet, apiKey *string) {
20+
f.StringVarP(apiKey, "api-key", "a", cli.Env(cli.EnvAPIKey), "API key to authenticate to the KES server")
21+
}
22+
23+
func flagsOutputJSON(f *flag.FlagSet, jsonOutput *bool) {
24+
f.BoolVar(jsonOutput, "json", false, "Print output in JSON format")
25+
}
26+
27+
func flagsServer(f *flag.FlagSet, host *string) {
28+
f.StringVarP(host, "server", "s", cli.Env(cli.EnvServer), "Use the server HOST[:PORT]")
29+
}

cmd/kes/identity.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ func newIdentityCmd(args []string) {
337337
}
338338

339339
bold := tui.NewStyle()
340-
if isTerm(os.Stdout) {
340+
if cli.IsTerminal() {
341341
bold = bold.Bold(true)
342342
}
343343
var buffer strings.Builder
@@ -418,7 +418,7 @@ func ofIdentityCmd(args []string) {
418418
h := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
419419
identity = kes.Identity(hex.EncodeToString(h[:]))
420420
}
421-
if isTerm(os.Stdout) {
421+
if cli.IsTerminal() {
422422
var buffer strings.Builder
423423
fmt.Fprintln(&buffer, "Identity:")
424424
fmt.Fprintln(&buffer)
@@ -491,7 +491,9 @@ func infoIdentityCmd(args []string) {
491491
dotDenyStyle = dotDenyStyle.Foreground(ColorDotDeny)
492492
}
493493

494-
client := newClient(insecureSkipVerify)
494+
client := newClient(config{
495+
InsecureSkipVerify: insecureSkipVerify,
496+
})
495497
if cmd.NArg() == 0 {
496498
info, policy, err := client.DescribeSelf(ctx)
497499
if err != nil {
@@ -619,7 +621,9 @@ func lsIdentityCmd(args []string) {
619621
ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
620622
defer cancelCtx()
621623

622-
enclave := newClient(insecureSkipVerify)
624+
enclave := newClient(config{
625+
InsecureSkipVerify: insecureSkipVerify,
626+
})
623627
iter := &kes.ListIter[kes.Identity]{
624628
NextFunc: enclave.ListIdentities,
625629
}

cmd/kes/key.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ func createKeyCmd(args []string) {
116116
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
117117
defer cancel()
118118

119-
client := newClient(insecureSkipVerify)
119+
client := newClient(config{
120+
InsecureSkipVerify: insecureSkipVerify,
121+
})
120122
for _, name := range cmd.Args() {
121123
if err := client.CreateKey(ctx, name); err != nil {
122124
if errors.Is(err, context.Canceled) {
@@ -169,7 +171,9 @@ func importKeyCmd(args []string) {
169171
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
170172
defer cancel()
171173

172-
enclave := newClient(insecureSkipVerify)
174+
enclave := newClient(config{
175+
InsecureSkipVerify: insecureSkipVerify,
176+
})
173177
if err = enclave.ImportKey(ctx, name, &kes.ImportKeyRequest{Key: key}); err != nil {
174178
if errors.Is(err, context.Canceled) {
175179
os.Exit(1)
@@ -229,7 +233,9 @@ func describeKeyCmd(args []string) {
229233
defer cancelCtx()
230234

231235
name := cmd.Arg(0)
232-
client := newClient(insecureSkipVerify)
236+
client := newClient(config{
237+
InsecureSkipVerify: insecureSkipVerify,
238+
})
233239
info, err := client.DescribeKey(ctx, name)
234240
if err != nil {
235241
if errors.Is(err, context.Canceled) {
@@ -308,7 +314,9 @@ func lsKeyCmd(args []string) {
308314
ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
309315
defer cancelCtx()
310316

311-
enclave := newClient(insecureSkipVerify)
317+
enclave := newClient(config{
318+
InsecureSkipVerify: insecureSkipVerify,
319+
})
312320
iter := &kes.ListIter[string]{
313321
NextFunc: enclave.ListKeys,
314322
}
@@ -380,7 +388,9 @@ func rmKeyCmd(args []string) {
380388
ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
381389
defer cancelCtx()
382390

383-
client := newClient(insecureSkipVerify)
391+
client := newClient(config{
392+
InsecureSkipVerify: insecureSkipVerify,
393+
})
384394
for _, name := range cmd.Args() {
385395
if err := client.DeleteKey(ctx, name); err != nil {
386396
if errors.Is(err, context.Canceled) {
@@ -436,7 +446,9 @@ func encryptKeyCmd(args []string) {
436446
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
437447
defer cancel()
438448

439-
client := newClient(insecureSkipVerify)
449+
client := newClient(config{
450+
InsecureSkipVerify: insecureSkipVerify,
451+
})
440452
ciphertext, err := client.Encrypt(ctx, name, []byte(message), nil)
441453
if err != nil {
442454
if errors.Is(err, context.Canceled) {
@@ -445,7 +457,7 @@ func encryptKeyCmd(args []string) {
445457
cli.Fatalf("failed to encrypt message: %v", err)
446458
}
447459

448-
if isTerm(os.Stdout) {
460+
if cli.IsTerminal() {
449461
fmt.Printf("\nciphertext: %s\n", base64.StdEncoding.EncodeToString(ciphertext))
450462
} else {
451463
fmt.Printf(`{"ciphertext":"%s"}`, base64.StdEncoding.EncodeToString(ciphertext))
@@ -509,7 +521,9 @@ func decryptKeyCmd(args []string) {
509521
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
510522
defer cancel()
511523

512-
client := newClient(insecureSkipVerify)
524+
client := newClient(config{
525+
InsecureSkipVerify: insecureSkipVerify,
526+
})
513527
plaintext, err := client.Decrypt(ctx, name, ciphertext, associatedData)
514528
if err != nil {
515529
if errors.Is(err, context.Canceled) {
@@ -518,7 +532,7 @@ func decryptKeyCmd(args []string) {
518532
cli.Fatalf("failed to decrypt ciphertext: %v", err)
519533
}
520534

521-
if isTerm(os.Stdout) {
535+
if cli.IsTerminal() {
522536
fmt.Printf("\nplaintext: %s\n", base64.StdEncoding.EncodeToString(plaintext))
523537
} else {
524538
fmt.Printf(`{"plaintext":"%s"}`, base64.StdEncoding.EncodeToString(plaintext))
@@ -575,7 +589,9 @@ func dekCmd(args []string) {
575589
ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
576590
defer cancelCtx()
577591

578-
client := newClient(insecureSkipVerify)
592+
client := newClient(config{
593+
InsecureSkipVerify: insecureSkipVerify,
594+
})
579595
key, err := client.GenerateKey(ctx, name, associatedData)
580596
if err != nil {
581597
if errors.Is(err, context.Canceled) {
@@ -588,7 +604,7 @@ func dekCmd(args []string) {
588604
plaintext = base64.StdEncoding.EncodeToString(key.Plaintext)
589605
ciphertext = base64.StdEncoding.EncodeToString(key.Ciphertext)
590606
)
591-
if isTerm(os.Stdout) {
607+
if cli.IsTerminal() {
592608
const format = "\nplaintext: %s\nciphertext: %s\n"
593609
fmt.Printf(format, plaintext, ciphertext)
594610
} else {

cmd/kes/log.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ func logCmd(args []string) {
6767
auditFlag = !auditFlag
6868
}
6969

70-
client := newClient(insecureSkipVerify)
70+
client := newClient(config{
71+
InsecureSkipVerify: insecureSkipVerify,
72+
})
7173
ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
7274
defer cancelCtx()
7375

@@ -125,7 +127,7 @@ func printAuditLog(stream *kes.AuditStream) {
125127
format = "%02d:%02d:%02d %s %s %s %s %s\n"
126128
)
127129

128-
if isTerm(os.Stdout) {
130+
if cli.IsTerminal() {
129131
fmt.Println(tui.NewStyle().Bold(true).Underline(true).Render(header))
130132
} else {
131133
fmt.Println(header)

cmd/kes/ls.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2024 - MinIO, Inc. All rights reserved.
2+
// Use of this source code is governed by the AGPLv3
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"encoding/json"
10+
"fmt"
11+
"io"
12+
"os"
13+
"os/signal"
14+
"slices"
15+
"strings"
16+
17+
tui "github.com/charmbracelet/lipgloss"
18+
"github.com/minio/kes/internal/cli"
19+
"github.com/minio/kms-go/kes"
20+
flag "github.com/spf13/pflag"
21+
)
22+
23+
const lsUsage = `Usage:
24+
kes ls [-a KEY] [-k] [--json] [-i] [-p] [-s HOST[:PORT]] [PREFIX]
25+
26+
Options:
27+
-a, --api-key KEY API key to authenticate to the KES server.
28+
Defaults to $MINIO_KES_API_KEY.
29+
-s, --server HOST[:PORT] Use the server HOST[:PORT] instead of
30+
$MINIO_KES_SERVER.
31+
--json Print output in JSON format.
32+
-i, --identity List identities.
33+
-p, --policy List policy names.
34+
-k, --insecure Skip server certificate verification.
35+
`
36+
37+
func ls(args []string) {
38+
var (
39+
apiKey string
40+
skipVerify bool
41+
jsonOutput bool
42+
host string
43+
policies bool
44+
identities bool
45+
)
46+
47+
flags := flag.NewFlagSet(args[0], flag.ContinueOnError)
48+
flagsAPIKey(flags, &apiKey)
49+
flagsInsecureSkipVerify(flags, &skipVerify)
50+
flagsOutputJSON(flags, &jsonOutput)
51+
flagsServer(flags, &host)
52+
flags.BoolVarP(&policies, "policy", "p", false, "")
53+
flags.BoolVarP(&identities, "identity", "i", false, "")
54+
flags.Usage = func() { fmt.Fprint(os.Stderr, lsUsage) }
55+
56+
if err := flags.Parse(args[1:]); err != nil {
57+
cli.Exit(err)
58+
}
59+
if flags.NArg() > 1 {
60+
cli.Exit("too many arguments")
61+
}
62+
if identities && policies {
63+
cli.Exit("'-p / --policy' and '-i / --identity' must not be used at the same time")
64+
}
65+
66+
// Define functions for listing keys, identities and policies.
67+
// All a []string since we want to print the elements anyway.
68+
listKeys := func(ctx context.Context, client *kes.Client, prefix string) ([]string, error) {
69+
iter := kes.ListIter[string]{
70+
NextFunc: client.ListKeys,
71+
}
72+
var names []string
73+
for name, err := iter.SeekTo(ctx, prefix); err != io.EOF; name, err = iter.Next(ctx) {
74+
if err != nil {
75+
return nil, err
76+
}
77+
names = append(names, name)
78+
}
79+
return names, nil
80+
}
81+
listIdentities := func(ctx context.Context, client *kes.Client, prefix string) ([]string, error) {
82+
iter := kes.ListIter[kes.Identity]{
83+
NextFunc: client.ListIdentities,
84+
}
85+
var names []string
86+
for id, err := iter.SeekTo(ctx, prefix); err != io.EOF; id, err = iter.Next(ctx) {
87+
if err != nil {
88+
return nil, err
89+
}
90+
names = append(names, id.String())
91+
}
92+
return names, nil
93+
}
94+
listPolicies := func(ctx context.Context, client *kes.Client, prefix string) ([]string, error) {
95+
iter := kes.ListIter[string]{
96+
NextFunc: client.ListPolicies,
97+
}
98+
var names []string
99+
for name, err := iter.SeekTo(ctx, prefix); err != io.EOF; name, err = iter.Next(ctx) {
100+
if err != nil {
101+
return nil, err
102+
}
103+
names = append(names, name)
104+
}
105+
return names, nil
106+
}
107+
108+
var prefix string
109+
if flags.NArg() == 1 {
110+
prefix = flags.Arg(0)
111+
}
112+
113+
client := newClient(config{
114+
Endpoint: host,
115+
APIKey: apiKey,
116+
InsecureSkipVerify: skipVerify,
117+
})
118+
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
119+
defer cancel()
120+
121+
var (
122+
names []string
123+
err error
124+
)
125+
switch {
126+
case identities:
127+
names, err = listIdentities(ctx, client, prefix)
128+
case policies:
129+
names, err = listPolicies(ctx, client, prefix)
130+
default:
131+
names, err = listKeys(ctx, client, prefix)
132+
}
133+
if err != nil {
134+
cli.Exit(err)
135+
}
136+
slices.Sort(names)
137+
138+
if jsonOutput {
139+
if err := json.NewEncoder(os.Stdout).Encode(names); err != nil {
140+
cli.Exit(err)
141+
}
142+
return
143+
}
144+
if len(names) == 0 {
145+
return
146+
}
147+
148+
buf := &strings.Builder{}
149+
switch s := tui.NewStyle().Underline(true); {
150+
case identities:
151+
fmt.Fprintln(buf, s.Render("Identity"))
152+
case policies:
153+
fmt.Fprintln(buf, s.Render("Policy"))
154+
default:
155+
fmt.Fprintln(buf, s.Render("Key"))
156+
}
157+
for _, name := range names {
158+
fmt.Fprintln(buf, name)
159+
}
160+
fmt.Print(buf)
161+
}

0 commit comments

Comments
 (0)