Skip to content

Commit

Permalink
cmd: add enclave commands
Browse files Browse the repository at this point in the history
This commit adds the new enclave command
group with two sub-commands:
 - `kes enclave create`
 - `kes enclave rm`

Now, the KES system admin can create and delete
enclaves programmatically via the CLI.

Signed-off-by: Andreas Auernhammer <hi@aead.dev>
  • Loading branch information
aead committed Sep 9, 2022
1 parent c456b37 commit 752f186
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 2 deletions.
67 changes: 66 additions & 1 deletion client.go
Expand Up @@ -5,6 +5,7 @@
package kes

import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
Expand All @@ -13,6 +14,7 @@ import (
"math"
"net"
"net/http"
"net/url"
"path"
"strings"
"time"
Expand Down Expand Up @@ -225,10 +227,64 @@ func (c *Client) APIs(ctx context.Context) ([]API, error) {
return apis, nil
}

// CreateEnclave creates a new enclave with the given
// identity as enclave admin. Only the KES system
// admin can create new enclaves.
//
// It returns ErrEnclaveExists if the enclave already
// exists.
func (c *Client) CreateEnclave(ctx context.Context, name string, admin Identity) error {
const (
APIPath = "/v1/enclave/create"
Method = http.MethodPost
StatusOK = http.StatusOK
)
type Request struct {
Admin Identity `json:"admin"`
}
body, err := json.Marshal(Request{
Admin: admin,
})
if err != nil {
return err
}
client := retry(c.HTTPClient)
resp, err := client.Send(ctx, Method, c.Endpoints, join(APIPath, name), bytes.NewReader(body))
if err != nil {
return err
}
if resp.StatusCode != StatusOK {
return parseErrorResponse(resp)
}
return nil
}

// DeleteEnclave delete the specified enclave. Only the
// KES system admin can delete enclaves.
//
// It returns ErrEnclaveNotFound if the enclave does not
// exist.
func (c *Client) DeleteEnclave(ctx context.Context, name string) error {
const (
APIPath = "/v1/enclave/delete"
Method = http.MethodDelete
StatusOK = http.StatusOK
)
client := retry(c.HTTPClient)
resp, err := client.Send(ctx, Method, c.Endpoints, join(APIPath, name), nil)
if err != nil {
return err
}
if resp.StatusCode != StatusOK {
return parseErrorResponse(resp)
}
return nil
}

// CreateKey creates a new cryptographic key. The key will
// be generated by the KES server.
//
// It returns ErrKeyExists if a key with the same key already
// It returns ErrKeyExists if a key with the same name already
// exists.
func (c *Client) CreateKey(ctx context.Context, name string) error {
enclave := Enclave{
Expand Down Expand Up @@ -614,6 +670,15 @@ func (c *Client) Metrics(ctx context.Context) (Metric, error) {
return metric, nil
}

// Join joins any number of arguments with the API path.
// All arguments are path-escaped before joining.
func join(api string, args ...string) string {
for _, arg := range args {
api = path.Join(api, url.PathEscape(arg))
}
return api
}

// endpoint returns an endpoint URL starting with the
// given endpoint followed by the path elements.
//
Expand Down
148 changes: 148 additions & 0 deletions cmd/kes/enclave.go
@@ -0,0 +1,148 @@
// 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 (
"context"
"errors"
"fmt"
"os"
"os/signal"

"github.com/minio/kes"
"github.com/minio/kes/internal/cli"
flag "github.com/spf13/pflag"
)

const enclaveCmdUsage = `Usage:
kes enclave <command>
Commands:
create Create a new enclave.
rm Delete an enclave.
Options:
-h, --help Print command line options.
`

func enclaveCmd(args []string) {
cmd := flag.NewFlagSet(args[0], flag.ContinueOnError)
cmd.Usage = func() { fmt.Fprint(os.Stderr, enclaveCmdUsage) }

subCmds := commands{
"create": createEnclaveCmd,
"rm": deleteEnclaveCmd,
}

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 enclave --help'", err)
}
if cmd.NArg() > 0 {
cli.Fatalf("%q is not a enclave command. See 'kes enclave --help'", cmd.Arg(0))
}
cmd.Usage()
os.Exit(2)
}

const createEnclaveCmdUsage = `Usage:
kes enclave create [options] <name> <identity>
Options:
-k, --insecure Skip TLS certificate validation.
-h, --help Print command line options.
Examples:
$ kes enclave create tenant-1 5f2f4ef3e0e340a07fc330f58ef0a1c4d661e564ab10795f9231f75fcfe572f1
`

func createEnclaveCmd(args []string) {
cmd := flag.NewFlagSet(args[0], flag.ContinueOnError)
cmd.Usage = func() { fmt.Fprint(os.Stderr, createEnclaveCmdUsage) }

var insecureSkipVerify bool
cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation")
if err := cmd.Parse(args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(2)
}
cli.Fatalf("%v. See 'kes enclave create --help'", err)
}

switch {
case cmd.NArg() == 0:
cli.Fatal("no enclave name specified. See 'kes enclave create --help'")
case cmd.NArg() == 1:
cli.Fatal("no admin identity specified. See 'kes enclave create --help'")
case cmd.NArg() > 2:
cli.Fatal("too many arguments. See 'kes enclave create --help'")
}

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel()

name := cmd.Arg(0)
admin := cmd.Arg(1)
client := newClient(insecureSkipVerify)
if err := client.CreateEnclave(ctx, name, kes.Identity(admin)); err != nil {
if errors.Is(err, context.Canceled) {
os.Exit(1)
}
cli.Fatalf("failed to create enclave '%s': %v", name, err)
}
}

const deleteEnclaveCmdUsage = `Usage:
kes enclave rm [options] <name>...
Options:
-k, --insecure Skip TLS certificate validation.
-h, --help Print command line options.
Examples:
$ kes enclave rm tenant-1
`

func deleteEnclaveCmd(args []string) {
cmd := flag.NewFlagSet(args[0], flag.ContinueOnError)
cmd.Usage = func() { fmt.Fprint(os.Stderr, deleteEnclaveCmdUsage) }

var insecureSkipVerify bool
cmd.BoolVarP(&insecureSkipVerify, "insecure", "k", false, "Skip TLS certificate validation")
if err := cmd.Parse(args[1:]); err != nil {
if errors.Is(err, flag.ErrHelp) {
os.Exit(2)
}
cli.Fatalf("%v. See 'kes enclave delete --help'", err)
}

if cmd.NArg() == 0 {
cli.Fatal("no enclave name specified. See 'kes enclave delete --help'")
}

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel()

client := newClient(insecureSkipVerify)
for _, name := range cmd.Args() {
if err := client.DeleteEnclave(ctx, name); err != nil {
if errors.Is(err, context.Canceled) {
os.Exit(1)
}
cli.Fatalf("failed to delete enclave '%s': %v", name, err)
}
}
}
2 changes: 2 additions & 0 deletions cmd/kes/main.go
Expand Up @@ -30,6 +30,7 @@ Commands:
server Start a KES server.
init Initialize a stateful KES server or cluster.
enclave Manage KES enclaves.
key Manage cryptographic keys.
policy Manage KES policies.
identity Manage KES identities.
Expand All @@ -54,6 +55,7 @@ func main() {
"server": serverCmd,
"init": initCmd,

"enclave": enclaveCmd,
"key": keyCmd,
"policy": policyCmd,
"identity": identityCmd,
Expand Down
2 changes: 1 addition & 1 deletion enclave.go
Expand Up @@ -33,7 +33,7 @@ type Enclave struct {
// CreateKey creates a new cryptographic key. The key will
// be generated by the KES server.
//
// It returns ErrKeyExists if a key with the same key already
// It returns ErrKeyExists if a key with the same name already
// exists.
func (e *Enclave) CreateKey(ctx context.Context, name string) error {
const (
Expand Down

0 comments on commit 752f186

Please sign in to comment.