From ef5d2f86b750b1a323b5251929649839f8f093fa Mon Sep 17 00:00:00 2001 From: Andreas Auernhammer Date: Thu, 16 May 2024 08:13:18 +0200 Subject: [PATCH] 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 --- cmd/kes/color-option.go | 4 +- cmd/kes/flags.go | 29 +++++++ cmd/kes/identity.go | 12 ++- cmd/kes/key.go | 38 ++++++--- cmd/kes/log.go | 6 +- cmd/kes/ls.go | 161 +++++++++++++++++++++++++++++++++++++ cmd/kes/main.go | 172 ++++++++++++++++++++++------------------ cmd/kes/metric.go | 6 +- cmd/kes/policy.go | 18 +++-- cmd/kes/status.go | 6 +- internal/cli/env.go | 81 +++++++++++++++++++ 11 files changed, 426 insertions(+), 107 deletions(-) create mode 100644 cmd/kes/flags.go create mode 100644 cmd/kes/ls.go diff --git a/cmd/kes/color-option.go b/cmd/kes/color-option.go index e5c14179..04c316ab 100644 --- a/cmd/kes/color-option.go +++ b/cmd/kes/color-option.go @@ -6,10 +6,10 @@ package main import ( "errors" - "os" "strings" tui "github.com/charmbracelet/lipgloss" + "github.com/minio/kes/internal/cli" "github.com/muesli/termenv" flag "github.com/spf13/pflag" ) @@ -29,7 +29,7 @@ var _ flag.Value = (*colorOption)(nil) func (c *colorOption) Colorize() bool { v := strings.ToLower(c.value) - return v == "always" || ((v == "auto" || v == "") && isTerm(os.Stdout)) + return v == "always" || ((v == "auto" || v == "") && cli.IsTerminal()) } func (c *colorOption) String() string { return c.value } diff --git a/cmd/kes/flags.go b/cmd/kes/flags.go new file mode 100644 index 00000000..f37542dc --- /dev/null +++ b/cmd/kes/flags.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/minio/kes/internal/cli" + flag "github.com/spf13/pflag" +) + +// Use register functions for common flags exposed by +// many commands. Common flags should have common names +// to make command usage consistent. + +// flagsInsecureSkipVerify adds a bool flag '-k, --insecure' +// that sets insecureSkipVerify to true if provided on the +// command line. +func flagsInsecureSkipVerify(f *flag.FlagSet, insecureSkipVerify *bool) { + f.BoolVarP(insecureSkipVerify, "insecure", "k", false, "Skip server certificate verification") +} + +func flagsAPIKey(f *flag.FlagSet, apiKey *string) { + f.StringVarP(apiKey, "api-key", "a", cli.Env(cli.EnvAPIKey), "API key to authenticate to the KES server") +} + +func flagsOutputJSON(f *flag.FlagSet, jsonOutput *bool) { + f.BoolVar(jsonOutput, "json", false, "Print output in JSON format") +} + +func flagsServer(f *flag.FlagSet, host *string) { + f.StringVarP(host, "server", "s", cli.Env(cli.EnvServer), "Use the server HOST[:PORT]") +} diff --git a/cmd/kes/identity.go b/cmd/kes/identity.go index 469def61..8aad418a 100644 --- a/cmd/kes/identity.go +++ b/cmd/kes/identity.go @@ -337,7 +337,7 @@ func newIdentityCmd(args []string) { } bold := tui.NewStyle() - if isTerm(os.Stdout) { + if cli.IsTerminal() { bold = bold.Bold(true) } var buffer strings.Builder @@ -418,7 +418,7 @@ func ofIdentityCmd(args []string) { h := sha256.Sum256(cert.RawSubjectPublicKeyInfo) identity = kes.Identity(hex.EncodeToString(h[:])) } - if isTerm(os.Stdout) { + if cli.IsTerminal() { var buffer strings.Builder fmt.Fprintln(&buffer, "Identity:") fmt.Fprintln(&buffer) @@ -491,7 +491,9 @@ func infoIdentityCmd(args []string) { dotDenyStyle = dotDenyStyle.Foreground(ColorDotDeny) } - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) if cmd.NArg() == 0 { info, policy, err := client.DescribeSelf(ctx) if err != nil { @@ -619,7 +621,9 @@ func lsIdentityCmd(args []string) { ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() - enclave := newClient(insecureSkipVerify) + enclave := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) iter := &kes.ListIter[kes.Identity]{ NextFunc: enclave.ListIdentities, } diff --git a/cmd/kes/key.go b/cmd/kes/key.go index b7fd597b..b79915f2 100644 --- a/cmd/kes/key.go +++ b/cmd/kes/key.go @@ -116,7 +116,9 @@ func createKeyCmd(args []string) { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) for _, name := range cmd.Args() { if err := client.CreateKey(ctx, name); err != nil { if errors.Is(err, context.Canceled) { @@ -169,7 +171,9 @@ func importKeyCmd(args []string) { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() - enclave := newClient(insecureSkipVerify) + enclave := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) if err = enclave.ImportKey(ctx, name, &kes.ImportKeyRequest{Key: key}); err != nil { if errors.Is(err, context.Canceled) { os.Exit(1) @@ -229,7 +233,9 @@ func describeKeyCmd(args []string) { defer cancelCtx() name := cmd.Arg(0) - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) info, err := client.DescribeKey(ctx, name) if err != nil { if errors.Is(err, context.Canceled) { @@ -308,7 +314,9 @@ func lsKeyCmd(args []string) { ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() - enclave := newClient(insecureSkipVerify) + enclave := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) iter := &kes.ListIter[string]{ NextFunc: enclave.ListKeys, } @@ -380,7 +388,9 @@ func rmKeyCmd(args []string) { ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) for _, name := range cmd.Args() { if err := client.DeleteKey(ctx, name); err != nil { if errors.Is(err, context.Canceled) { @@ -436,7 +446,9 @@ func encryptKeyCmd(args []string) { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) ciphertext, err := client.Encrypt(ctx, name, []byte(message), nil) if err != nil { if errors.Is(err, context.Canceled) { @@ -445,7 +457,7 @@ func encryptKeyCmd(args []string) { cli.Fatalf("failed to encrypt message: %v", err) } - if isTerm(os.Stdout) { + if cli.IsTerminal() { fmt.Printf("\nciphertext: %s\n", base64.StdEncoding.EncodeToString(ciphertext)) } else { fmt.Printf(`{"ciphertext":"%s"}`, base64.StdEncoding.EncodeToString(ciphertext)) @@ -509,7 +521,9 @@ func decryptKeyCmd(args []string) { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) plaintext, err := client.Decrypt(ctx, name, ciphertext, associatedData) if err != nil { if errors.Is(err, context.Canceled) { @@ -518,7 +532,7 @@ func decryptKeyCmd(args []string) { cli.Fatalf("failed to decrypt ciphertext: %v", err) } - if isTerm(os.Stdout) { + if cli.IsTerminal() { fmt.Printf("\nplaintext: %s\n", base64.StdEncoding.EncodeToString(plaintext)) } else { fmt.Printf(`{"plaintext":"%s"}`, base64.StdEncoding.EncodeToString(plaintext)) @@ -575,7 +589,9 @@ func dekCmd(args []string) { ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) key, err := client.GenerateKey(ctx, name, associatedData) if err != nil { if errors.Is(err, context.Canceled) { @@ -588,7 +604,7 @@ func dekCmd(args []string) { plaintext = base64.StdEncoding.EncodeToString(key.Plaintext) ciphertext = base64.StdEncoding.EncodeToString(key.Ciphertext) ) - if isTerm(os.Stdout) { + if cli.IsTerminal() { const format = "\nplaintext: %s\nciphertext: %s\n" fmt.Printf(format, plaintext, ciphertext) } else { diff --git a/cmd/kes/log.go b/cmd/kes/log.go index e8806ea4..b170e7a9 100644 --- a/cmd/kes/log.go +++ b/cmd/kes/log.go @@ -67,7 +67,9 @@ func logCmd(args []string) { auditFlag = !auditFlag } - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() @@ -125,7 +127,7 @@ func printAuditLog(stream *kes.AuditStream) { format = "%02d:%02d:%02d %s %s %s %s %s\n" ) - if isTerm(os.Stdout) { + if cli.IsTerminal() { fmt.Println(tui.NewStyle().Bold(true).Underline(true).Render(header)) } else { fmt.Println(header) diff --git a/cmd/kes/ls.go b/cmd/kes/ls.go new file mode 100644 index 00000000..1992fc77 --- /dev/null +++ b/cmd/kes/ls.go @@ -0,0 +1,161 @@ +// Copyright 2024 - 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 ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "os/signal" + "slices" + "strings" + + tui "github.com/charmbracelet/lipgloss" + "github.com/minio/kes/internal/cli" + "github.com/minio/kms-go/kes" + flag "github.com/spf13/pflag" +) + +const lsUsage = `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. +` + +func ls(args []string) { + var ( + apiKey string + skipVerify bool + jsonOutput bool + host string + policies bool + identities bool + ) + + flags := flag.NewFlagSet(args[0], flag.ContinueOnError) + flagsAPIKey(flags, &apiKey) + flagsInsecureSkipVerify(flags, &skipVerify) + flagsOutputJSON(flags, &jsonOutput) + flagsServer(flags, &host) + flags.BoolVarP(&policies, "policy", "p", false, "") + flags.BoolVarP(&identities, "identity", "i", false, "") + flags.Usage = func() { fmt.Fprint(os.Stderr, lsUsage) } + + if err := flags.Parse(args[1:]); err != nil { + cli.Exit(err) + } + if flags.NArg() > 1 { + cli.Exit("too many arguments") + } + if identities && policies { + cli.Exit("'-p / --policy' and '-i / --identity' must not be used at the same time") + } + + // Define functions for listing keys, identities and policies. + // All a []string since we want to print the elements anyway. + listKeys := func(ctx context.Context, client *kes.Client, prefix string) ([]string, error) { + iter := kes.ListIter[string]{ + NextFunc: client.ListKeys, + } + var names []string + for name, err := iter.SeekTo(ctx, prefix); err != io.EOF; name, err = iter.Next(ctx) { + if err != nil { + return nil, err + } + names = append(names, name) + } + return names, nil + } + listIdentities := func(ctx context.Context, client *kes.Client, prefix string) ([]string, error) { + iter := kes.ListIter[kes.Identity]{ + NextFunc: client.ListIdentities, + } + var names []string + for id, err := iter.SeekTo(ctx, prefix); err != io.EOF; id, err = iter.Next(ctx) { + if err != nil { + return nil, err + } + names = append(names, id.String()) + } + return names, nil + } + listPolicies := func(ctx context.Context, client *kes.Client, prefix string) ([]string, error) { + iter := kes.ListIter[string]{ + NextFunc: client.ListPolicies, + } + var names []string + for name, err := iter.SeekTo(ctx, prefix); err != io.EOF; name, err = iter.Next(ctx) { + if err != nil { + return nil, err + } + names = append(names, name) + } + return names, nil + } + + var prefix string + if flags.NArg() == 1 { + prefix = flags.Arg(0) + } + + client := newClient(config{ + Endpoint: host, + APIKey: apiKey, + InsecureSkipVerify: skipVerify, + }) + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + var ( + names []string + err error + ) + switch { + case identities: + names, err = listIdentities(ctx, client, prefix) + case policies: + names, err = listPolicies(ctx, client, prefix) + default: + names, err = listKeys(ctx, client, prefix) + } + if err != nil { + cli.Exit(err) + } + slices.Sort(names) + + if jsonOutput { + if err := json.NewEncoder(os.Stdout).Encode(names); err != nil { + cli.Exit(err) + } + return + } + if len(names) == 0 { + return + } + + buf := &strings.Builder{} + switch s := tui.NewStyle().Underline(true); { + case identities: + fmt.Fprintln(buf, s.Render("Identity")) + case policies: + fmt.Fprintln(buf, s.Render("Policy")) + default: + fmt.Fprintln(buf, s.Render("Key")) + } + for _, name := range names { + fmt.Fprintln(buf, name) + } + fmt.Print(buf) +} diff --git a/cmd/kes/main.go b/cmd/kes/main.go index 6cbf74a6..0801dc86 100644 --- a/cmd/kes/main.go +++ b/cmd/kes/main.go @@ -33,6 +33,7 @@ const usage = `Usage: Commands: server Start a KES server. + ls List keys, policies and identites. key Manage cryptographic keys. policy Manage KES policies. identity Manage KES identities. @@ -61,6 +62,7 @@ func main() { subCmds := commands{ "server": serverCmd, + "ls": ls, "key": keyCmd, "policy": policyCmd, "identity": identityCmd, @@ -124,111 +126,125 @@ func main() { os.Exit(2) } -func newClient(insecureSkipVerify bool) *kes.Client { - const DefaultServer = "https://127.0.0.1:7373" - const ( - EnvServer = "KES_SERVER" - EnvAPIKey = "KES_API_KEY" - EnvClientKey = "KES_CLIENT_KEY" - EnvClientCert = "KES_CLIENT_CERT" - ) +// config is a structure containing configuration for a client. +type config struct { + Endpoint string + APIKey string + PrivateKeyFile string + CertificateFile string + InsecureSkipVerify bool +} - if apiKey, ok := os.LookupEnv(EnvAPIKey); ok { - if _, ok = os.LookupEnv(EnvClientCert); ok { - cli.Fatalf("two conflicting environment variables set: unset either '%s' or '%s'", EnvAPIKey, EnvClientCert) - } - if _, ok = os.LookupEnv(EnvClientKey); ok { - cli.Fatalf("two conflicting environment variables set: unset either '%s' or '%s'", EnvAPIKey, EnvClientKey) - } - key, err := kes.ParseAPIKey(apiKey) - if err != nil { - cli.Fatalf("invalid API key: %v", err) - } - cert, err := kes.GenerateCertificate(key) - if err != nil { - cli.Fatalf("failed to generate client certificate from API key: %v", err) - } +// newClient returns a new client using the given config. +// On error, it aborts the program using cli.Exit. +func newClient(conf config) *kes.Client { + var ( + endpoints = strings.Split(conf.Endpoint, ",") + apiKey = conf.APIKey + keyFile = conf.PrivateKeyFile + certFile = conf.CertificateFile + ) - addr := DefaultServer - if env, ok := os.LookupEnv(EnvServer); ok { - addr = env + if conf.Endpoint == "" { + endpoints = strings.Split(cli.Env(cli.EnvServer), ",") + } + for i := range endpoints { + endpoints[i] = strings.TrimSpace(endpoints[i]) + endpoints[i] = strings.TrimPrefix(endpoints[i], "http://") + if !strings.HasPrefix(endpoints[i], "https://") { + endpoints[i] = "https://" + endpoints[i] } - return kes.NewClientWithConfig(addr, &tls.Config{ - Certificates: []tls.Certificate{cert}, - InsecureSkipVerify: insecureSkipVerify, - }) + } + if len(endpoints) == 0 { + cli.Exitf("'%s' contains no hosts / IPs", cli.EnvServer) } - certPath, ok := os.LookupEnv(EnvClientCert) - if !ok { - cli.Fatalf("no TLS client certificate. Environment variable '%s' is not set", EnvClientCert) + if apiKey == "" { + apiKey = cli.Env(cli.EnvAPIKey) + } + if keyFile == "" { + keyFile = cli.Env(cli.EnvPrivateKey) } - if strings.TrimSpace(certPath) == "" { - cli.Fatalf("no TLS client certificate. Environment variable '%s' is empty", EnvClientCert) + if certFile == "" { + certFile = cli.Env(cli.EnvCertificate) } - keyPath, ok := os.LookupEnv(EnvClientKey) - if !ok { - cli.Fatalf("no TLS private key. Environment variable '%s' is not set", EnvClientKey) + if apiKey == "" && keyFile == "" && certFile == "" { + cli.Exitf("no API key specified. Consider setting %s", cli.EnvAPIKey) } - if strings.TrimSpace(keyPath) == "" { - cli.Fatalf("no TLS private key. Environment variable '%s' is empty", EnvClientKey) + if apiKey != "" && keyFile != "" { + cli.Exit("API key and private key file cannot be used at the same time") } - - certPem, err := os.ReadFile(certPath) - if err != nil { - cli.Fatalf("failed to load TLS certificate: %v", err) + if apiKey != "" && certFile != "" { + cli.Exit("API key and certificate file cannot be used at the same time") } - certPem, err = https.FilterPEM(certPem, func(b *pem.Block) bool { return b.Type == "CERTIFICATE" }) - if err != nil { - cli.Fatalf("failed to load TLS certificate: %v", err) + if keyFile != "" && certFile == "" { + cli.Exitf("no certificate file specified. Consider setting %s", cli.EnvCertificate) } - keyPem, err := os.ReadFile(keyPath) - if err != nil { - cli.Fatalf("failed to load TLS private key: %v", err) + if keyFile == "" && certFile != "" { + cli.Exitf("no private key file specified. Consider setting %s", cli.EnvPrivateKey) } - // Check whether the private key is encrypted. If so, ask the user - // to enter the password on the CLI. - privateKey, err := decodePrivateKey(keyPem) - if err != nil { - cli.Fatalf("failed to read TLS private key: %v", err) - } - if len(privateKey.Headers) > 0 && x509.IsEncryptedPEMBlock(privateKey) { - fmt.Fprint(os.Stderr, "Enter password for private key: ") - password, err := term.ReadPassword(int(os.Stderr.Fd())) + var cert tls.Certificate + if apiKey != "" { + key, err := kes.ParseAPIKey(apiKey) + if err != nil { + cli.Exitf("parsing API key: %v", err) + } + if cert, err = kes.GenerateCertificate(key); err != nil { + cli.Exitf("generating certificate: %v", err) + } + } else { + certPem, err := os.ReadFile(certFile) + if err != nil { + cli.Fatalf("reading certificate file: %v", err) + } + certPem, err = https.FilterPEM(certPem, func(b *pem.Block) bool { return b.Type == "CERTIFICATE" }) if err != nil { - cli.Fatalf("failed to read private key password: %v", err) + cli.Fatalf("reading certificate file: %v", err) + } + keyPem, err := os.ReadFile(keyFile) + if err != nil { + cli.Fatalf("reading private key file: %v", err) } - fmt.Fprintln(os.Stderr) // Add the newline again - decPrivateKey, err := x509.DecryptPEMBlock(privateKey, password) + // Check whether the private key is encrypted. If so, ask the user + // to enter the password on the CLI. + privateKey, err := decodePrivateKey(keyPem) if err != nil { - if errors.Is(err, x509.IncorrectPasswordError) { - cli.Fatalf("incorrect password") + cli.Fatalf("failed to read TLS private key: %v", err) + } + if len(privateKey.Headers) > 0 && x509.IsEncryptedPEMBlock(privateKey) { + fmt.Fprint(os.Stderr, "Enter password for private key: ") + password, err := term.ReadPassword(int(os.Stderr.Fd())) + if err != nil { + cli.Exitf("reading password: %v", err) } - cli.Fatalf("failed to decrypt private key: %v", err) + fmt.Fprintln(os.Stderr) // Add the newline again + + decPrivateKey, err := x509.DecryptPEMBlock(privateKey, password) + if err != nil { + if errors.Is(err, x509.IncorrectPasswordError) { + cli.Exit("incorrect password") + } + cli.Exitf("failed to decrypt private key: %v", err) + } + keyPem = pem.EncodeToMemory(&pem.Block{Type: privateKey.Type, Bytes: decPrivateKey}) } - keyPem = pem.EncodeToMemory(&pem.Block{Type: privateKey.Type, Bytes: decPrivateKey}) - } - cert, err := tls.X509KeyPair(certPem, keyPem) - if err != nil { - cli.Fatalf("failed to load TLS private key or certificate: %v", err) + if cert, err = tls.X509KeyPair(certPem, keyPem); err != nil { + cli.Exit(err) + } } - addr := DefaultServer - if env, ok := os.LookupEnv(EnvServer); ok { - addr = env - } - return kes.NewClientWithConfig(addr, &tls.Config{ + client := kes.NewClientWithConfig("", &tls.Config{ Certificates: []tls.Certificate{cert}, - InsecureSkipVerify: insecureSkipVerify, + InsecureSkipVerify: conf.InsecureSkipVerify, }) + client.Endpoints = endpoints + return client } -func isTerm(f *os.File) bool { return term.IsTerminal(int(f.Fd())) } - func decodePrivateKey(pemBlock []byte) (*pem.Block, error) { ErrNoPrivateKey := errors.New("no PEM-encoded private key found") diff --git a/cmd/kes/metric.go b/cmd/kes/metric.go index 8e1bc7f4..fbb0b031 100644 --- a/cmd/kes/metric.go +++ b/cmd/kes/metric.go @@ -52,11 +52,13 @@ func metricCmd(args []string) { cli.Fatal("too many arguments. See 'kes metric --help'") } - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() - if isTerm(os.Stdout) { + if cli.IsTerminal() { traceMetricsWithUI(ctx, client, rate) return } diff --git a/cmd/kes/policy.go b/cmd/kes/policy.go index 0ecc439b..fbe63703 100644 --- a/cmd/kes/policy.go +++ b/cmd/kes/policy.go @@ -113,7 +113,9 @@ func lsPolicyCmd(args []string) { ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() - enclave := newClient(insecureSkipVerify) + enclave := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) iter := &kes.ListIter[string]{ NextFunc: enclave.ListPolicies, } @@ -194,7 +196,9 @@ func infoPolicyCmd(args []string) { defer cancelCtx() name := cmd.Arg(0) - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) info, err := client.DescribePolicy(ctx, name) if err != nil { if errors.Is(err, context.Canceled) { @@ -204,7 +208,7 @@ func infoPolicyCmd(args []string) { } if jsonFlag { encoder := json.NewEncoder(os.Stdout) - if isTerm(os.Stdout) { + if cli.IsTerminal() { encoder.SetIndent("", " ") } if err = encoder.Encode(info); err != nil { @@ -269,7 +273,9 @@ func showPolicyCmd(args []string) { } name := cmd.Arg(0) - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) ctx, cancelCtx := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancelCtx() @@ -281,7 +287,7 @@ func showPolicyCmd(args []string) { } cli.Fatalf("failed to show policy '%s': %v", name, err) } - if !isTerm(os.Stdout) || jsonFlag { + if !cli.IsTerminal() || jsonFlag { type Response struct { Allow map[string]kes.Rule `json:"allow,omitempty"` Deny map[string]kes.Rule `json:"deny,omitempty"` @@ -289,7 +295,7 @@ func showPolicyCmd(args []string) { CreatedBy kes.Identity `json:"created_by,omitempty"` } encoder := json.NewEncoder(os.Stdout) - if isTerm(os.Stdout) { + if cli.IsTerminal() { encoder.SetIndent("", " ") } err = encoder.Encode(Response{ diff --git a/cmd/kes/status.go b/cmd/kes/status.go index 58820887..c5bb9679 100644 --- a/cmd/kes/status.go +++ b/cmd/kes/status.go @@ -66,7 +66,9 @@ func statusCmd(args []string) { cli.Fatal("too many arguments. See 'kes status --help'") } - client := newClient(insecureSkipVerify) + client := newClient(config{ + InsecureSkipVerify: insecureSkipVerify, + }) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) defer cancel() @@ -90,7 +92,7 @@ func statusCmd(args []string) { if jsonFlag { encoder := json.NewEncoder(os.Stdout) - if isTerm(os.Stdout) && !shortFlag { + if cli.IsTerminal() && !shortFlag { encoder.SetIndent("", " ") } if apiFlag { diff --git a/internal/cli/env.go b/internal/cli/env.go index 98586466..8199b651 100644 --- a/internal/cli/env.go +++ b/internal/cli/env.go @@ -4,6 +4,10 @@ package cli +import ( + "os" +) + // Environment variable used by the KES CLI. const ( // EnvServer is the server endpoint the client uses. If not set, @@ -12,4 +16,81 @@ const ( // EnvAPIKey is used by the client to authenticate to the server. EnvAPIKey = "MINIO_KES_API_KEY" + + EnvPrivateKey = "MINIO_KES_KEY_FILE" + + EnvCertificate = "MINIO_KES_CERT_FILE" ) + +// Env retrieves the value of the environment variable named by the key. +// It returns the value, which will be empty if the variable is not present. +func Env(key string) string { + switch key { + default: + return os.Getenv(key) + case EnvServer: + const ( + EnvServerLegacy = "KES_SERVER" + EnvServerMinIO = "MINIO_KMS_KES_ENDPOINT" + DefaultServer = "127.0.0.1:7373" + ) + if s, ok := os.LookupEnv(EnvServer); ok { + return s + } + if s, ok := os.LookupEnv(EnvServerLegacy); ok { + return s + } + if s, ok := os.LookupEnv(EnvServerMinIO); ok { + return s + } + return DefaultServer + + case EnvAPIKey: + const ( + EnvAPIKeyLegacy = "KES_API_KEY" + EnvAPIKeyMinIO = "MINIO_KMS_KES_API_KEY" + ) + if s, ok := os.LookupEnv(EnvAPIKey); ok { + return s + } + if s, ok := os.LookupEnv(EnvAPIKeyLegacy); ok { + return s + } + if s, ok := os.LookupEnv(EnvAPIKeyMinIO); ok { + return s + } + return "" + + case EnvPrivateKey: + const ( + EnvPrivateKeyLegacy = "KES_CLIENT_KEY" + EnvPrivateKeyMinIO = "MINIO_KES_CLIENT_KEY" + ) + if s, ok := os.LookupEnv(EnvPrivateKey); ok { + return s + } + if s, ok := os.LookupEnv(EnvPrivateKeyLegacy); ok { + return s + } + if s, ok := os.LookupEnv(EnvPrivateKeyMinIO); ok { + return s + } + return "" + + case EnvCertificate: + const ( + EnvCertificateLegacy = "KES_CLIENT_CERT" + EnvCertificateMinIO = "MINIO_KES_CLIENT_CERT" + ) + if s, ok := os.LookupEnv(EnvCertificate); ok { + return s + } + if s, ok := os.LookupEnv(EnvCertificateLegacy); ok { + return s + } + if s, ok := os.LookupEnv(EnvCertificateMinIO); ok { + return s + } + return "" + } +}