Skip to content

Commit

Permalink
cmd: add kes policy info command
Browse files Browse the repository at this point in the history
This commit adds the `kes policy info` command.
It prints information about a KES policy.

Further, this commit adds the SDK function
`DescribePolicy` that calls the `/v1/policy/describe`
API which already exists.

```
$ kes policy info minio
Name        minio
Date        2022-06-22 11:40:08
Created by  3ecfcdf38fcbe141ae26a1030f81e96b753365a46760ae6b578698a97c59fd22
```

Signed-off-by: Andreas Auernhammer <hi@aead.dev>
  • Loading branch information
aead committed Jul 6, 2022
1 parent e48fc2b commit 806e8a1
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 5 deletions.
10 changes: 10 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,16 @@ func (c *Client) SetPolicy(ctx context.Context, name string, policy *Policy) err
return enclave.SetPolicy(ctx, name, policy)
}

// DescribePolicy returns the PolicyInfo for the given policy.
// It returns ErrPolicyNotFound if no such policy exists.
func (c *Client) DescribePolicy(ctx context.Context, name string) (*PolicyInfo, error) {
enclave := Enclave{
endpoints: c.Endpoints,
client: retry(c.HTTPClient),
}
return enclave.DescribePolicy(ctx, name)
}

// GetPolicy returns the policy with the given name.
// It returns ErrPolicyNotFound if no such policy
// exists.
Expand Down
4 changes: 2 additions & 2 deletions cmd/kes/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,11 +513,10 @@ func lsIdentityCmd(args []string) {
pattern = cmd.Arg(0)
}

client := newClient(insecureSkipVerify)

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

client := newClient(insecureSkipVerify)
identities, err := client.ListIdentities(ctx, pattern)
if err != nil {
if errors.Is(err, context.Canceled) {
Expand All @@ -526,6 +525,7 @@ func lsIdentityCmd(args []string) {
cli.Fatalf("failed to list identities: %v", err)
}
defer identities.Close()

if jsonFlag {
if _, err = identities.WriteTo(os.Stdout); err != nil {
cli.Fatal(err)
Expand Down
84 changes: 84 additions & 0 deletions cmd/kes/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const policyCmdUsage = `Usage:
Commands:
create Create a new policy.
assign Assign a policy to identities.
info Get information about a policy.
ls List policies.
rm Remove a policy.
show Display a policy.
Expand All @@ -42,6 +43,7 @@ func policyCmd(args []string) {
subCmds := commands{
"create": createPolicyCmd,
"assign": assignPolicyCmd,
"info": infoPolicyCmd,
"ls": lsPolicyCmd,
"rm": rmPolicyCmd,
"show": showPolicyCmd,
Expand Down Expand Up @@ -315,6 +317,88 @@ func rmPolicyCmd(args []string) {
}
}

const infoPolicyCmdUsage = `Usage:
kes policy info [options] <name>
Options:
-k, --insecure Skip TLS certificate validation.
--json Print policy in JSON format.
--color <when> 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.
-h, --help Print command line options.
Examples:
$ kes policy info my-policy
`

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

var (
jsonFlag bool
colorFlag colorOption
insecureSkipVerify bool
)
cmd.BoolVar(&jsonFlag, "json", false, "Print policy in JSON format.")
cmd.Var(&colorFlag, "color", "Specify when to use colored output")
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 policy show --help'", err)
}
if cmd.NArg() == 0 {
cli.Fatal("no policy name specified. See 'kes policy show --help'")
}

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

name := cmd.Arg(0)
client := newClient(insecureSkipVerify)
info, err := client.DescribePolicy(ctx, name)
if err != nil {
if errors.Is(err, context.Canceled) {
os.Exit(1)
}
cli.Fatal(err)
}
if jsonFlag {
encoder := json.NewEncoder(os.Stdout)
if isTerm(os.Stdout) {
encoder.SetIndent("", " ")
}
if err = encoder.Encode(info); err != nil {
cli.Fatal(err)
}
} else {
var faint, policyStyle tui.Style
if colorFlag.Colorize() {
const ColorPolicy tui.Color = "#2e42d1"
faint = faint.Faint(true)
policyStyle = policyStyle.Foreground(ColorPolicy)
}
fmt.Println(faint.Render(fmt.Sprintf("%-11s", "Name")), policyStyle.Render(name))
if !info.CreatedAt.IsZero() {
year, month, day := info.CreatedAt.Local().Date()
hour, min, sec := info.CreatedAt.Local().Clock()
fmt.Println(
faint.Render(fmt.Sprintf("%-11s", "Date")),
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")), info.CreatedBy)
}
}
}

const showPolicyCmdUsage = `Usage:
kes policy show [options] <name>
Expand Down
37 changes: 34 additions & 3 deletions enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,38 @@ func (e *Enclave) SetPolicy(ctx context.Context, name string, policy *Policy) er
return nil
}

// DescribePolicy returns the PolicyInfo for the given policy.
// It returns ErrPolicyNotFound if no such policy exists.
func (e *Enclave) DescribePolicy(ctx context.Context, name string) (*PolicyInfo, error) {
const (
APIPath = "/v1/policy/describe"
Method = http.MethodGet
StatusOK = http.StatusOK
MaxResponseSize = 1 << 20 // 1 MiB
)
type Response struct {
CreatedAt time.Time `json:"created_at"`
CreatedBy Identity `json:"created_by"`
}
resp, err := e.client.Send(ctx, Method, e.endpoints, e.path(APIPath, name), nil)
if err != nil {
return nil, err
}
if resp.StatusCode != StatusOK {
return nil, parseErrorResponse(resp)
}

var response Response
if err = json.NewDecoder(io.LimitReader(resp.Body, MaxResponseSize)).Decode(&response); err != nil {
return nil, err
}
return &PolicyInfo{
Name: name,
CreatedAt: response.CreatedAt,
CreatedBy: response.CreatedBy,
}, nil
}

// GetPolicy returns the policy with the given name.
// It returns ErrPolicyNotFound if no such policy
// exists.
Expand Down Expand Up @@ -420,16 +452,15 @@ func (e *Enclave) GetPolicy(ctx context.Context, name string) (*Policy, error) {
if err = json.NewDecoder(io.LimitReader(resp.Body, MaxResponseSize)).Decode(&response); err != nil {
return nil, err
}
policy := &Policy{
return &Policy{
Allow: response.Allow,
Deny: response.Deny,
Info: PolicyInfo{
Name: name,
CreatedAt: response.CreatedAt,
CreatedBy: response.CreatedBy,
},
}
return policy, nil
}, nil
}

// DeletePolicy deletes the policy with the given name. Any
Expand Down
100 changes: 100 additions & 0 deletions kestest/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,106 @@ func TestSetPolicy(t *testing.T) {
}
}

var getPolicyTests = []struct {
Name string
Policy *kes.Policy
}{
{Name: "my-policy", Policy: &kes.Policy{}},
{
Name: "my-policy2",
Policy: &kes.Policy{
Allow: []string{"/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"},
},
},
}

func TestDescribePolicy(t *testing.T) {
ctx, cancel := testingContext(t)
defer cancel()

server := kestest.NewServer()
defer server.Close()

client := server.Client()
for i, test := range getPolicyTests {
if err := client.SetPolicy(ctx, test.Name, test.Policy); err != nil {
t.Fatalf("Test %d: failed to create policy: %v", i, err)
}
info, err := client.DescribePolicy(ctx, test.Name)
if err != nil {
t.Fatalf("Test %d: failed to describe policy: %v", i, err)
}
if info.Name != test.Name {
t.Fatalf("Test %d: policy name mismatch: got '%s' - want '%s'", i, info.Name, test.Name)
}
if info.CreatedAt.IsZero() {
t.Fatalf("Test %d: created_at timestamp not set", i)
}
if info.CreatedBy.IsUnknown() {
t.Fatalf("Test %d: created_by identity not set", i)
}
}
}

func TestGetPolicy(t *testing.T) {
ctx, cancel := testingContext(t)
defer cancel()

server := kestest.NewServer()
defer server.Close()

client := server.Client()
for i, test := range getPolicyTests {
if err := client.SetPolicy(ctx, test.Name, test.Policy); err != nil {
t.Fatalf("Test %d: failed to create policy: %v", i, err)
}
policy, err := client.GetPolicy(ctx, test.Name)
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() {
t.Fatalf("Test %d: created_at timestamp not set", i)
}
if policy.Info.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 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])
}
}
}
}

var selfDescribeTests = []struct {
Policy kes.Policy
}{
Expand Down

0 comments on commit 806e8a1

Please sign in to comment.