Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ bin/.DS_Store
.DS_Store
target/
.vscode/launch.json
tructl.yaml
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ The main goals are to:

## Usage

The CLI is configured via the `tructl.yaml`. There is an example provided in `example-tructl.yaml`.

Run `cp example-tructl.yaml tructl.yaml` to copy the example config when running the CLI.

## Development

### CLI
Expand Down
14 changes: 11 additions & 3 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ func unMarshalMetadata(m string) *common.MetadataMutable {
return nil
}

func getMetadata(labels []string) *common.MetadataMutable {
var metadata *common.MetadataMutable
func getMetadataMutable(labels []string) *common.MetadataMutable {
metadata := common.MetadataMutable{}
if len(labels) > 0 {
metadata.Labels = map[string]string{}
for _, label := range labels {
Expand All @@ -89,7 +89,7 @@ func getMetadata(labels []string) *common.MetadataMutable {
}
metadata.Labels[kv[0]] = kv[1]
}
return metadata
return &metadata
}
return nil
}
Expand All @@ -114,6 +114,14 @@ func HandleSuccess(command *cobra.Command, id string, t *table.Table, policyObje
cli.PrintSuccessTable(command, id, t)
}

// Adds reusable create/update label flags to a Policy command and the optional force-replace-labels flag for updates only
func injectLabelFlags(cmd *cobra.Command, isUpdate bool) {
cmd.Flags().StringSliceVarP(&metadataLabels, "label", "l", []string{}, "Optional metadata 'labels' in the format: key=value")
if isUpdate {
cmd.Flags().BoolVar(&forceReplaceMetadataLabels, "force-replace-labels", false, "Destructively replace entire set of existing metadata 'labels' with any provided to this command.")
}
}

func init() {
rootCmd.AddCommand(devCmd)
devCmd.AddCommand(designCmd)
Expand Down
270 changes: 270 additions & 0 deletions cmd/kas-registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
package cmd

import (
"fmt"
"strings"

"github.com/opentdf/platform/protocol/go/kasregistry"
"github.com/opentdf/tructl/pkg/cli"
"github.com/spf13/cobra"
)

var (
kasRegistry_crudCommands = []string{
kasRegistrysCreateCmd.Use,
kasRegistryGetCmd.Use,
kasRegistrysListCmd.Use,
kasRegistryUpdateCmd.Use,
kasRegistryDeleteCmd.Use,
}

// KasRegistryCmd is the command for managing KAS registrations
kasRegistryCmd = &cobra.Command{
Use: "kas-registry",
Short: "Manage Key Access Server registrations [" + strings.Join(kasRegistry_crudCommands, ", ") + "]",
Long: `
Manage Key Access Server registrations within the platform.

The Key Access Server (KAS) registry is a record of servers granting and maintaining public keys. The registry contains critical
information like each server's uri, its public key (which can be either local or at a remote uri), and any metadata about the server.
Key Access Servers grant keys for specified Attributes and their Values via Attribute Key Access Grants and Attribute Value
Key Access Grants.
`,
}

kasRegistryGetCmd = &cobra.Command{
Use: "get",
Short: "Get a registered Key Access Server by id",
Run: func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
id := flagHelper.GetRequiredString("id")

kas, err := h.GetKasRegistryEntry(id)
if err != nil {
errMsg := fmt.Sprintf("Could not find KAS registry entry (%s)", id)
cli.ExitWithNotFoundError(errMsg, err)
}

keyType := "Local"
key := kas.PublicKey.GetLocal()
if kas.PublicKey.GetRemote() != "" {
keyType = "Remote"
key = kas.PublicKey.GetRemote()
}

t := cli.NewTabular().
Rows([][]string{
{"Id", kas.Id},
// TODO: render labels [https://github.com/opentdf/tructl/issues/73]
{"URI", kas.Uri},
{"PublicKey Type", keyType},
{"PublicKey", key},
}...)
HandleSuccess(cmd, kas.Id, t, kas)
},
}

kasRegistrysListCmd = &cobra.Command{
Use: "list",
Short: "List KAS registry entries",
Run: func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

list, err := h.ListKasRegistryEntries()
if err != nil {
cli.ExitWithError("Could not get KAS registry entries", err)
}

t := cli.NewTable()
t.Headers("Id", "URI", "PublicKey Location", "PublicKey")
for _, kas := range list {
keyType := "Local"
key := kas.PublicKey.GetLocal()
if kas.PublicKey.GetRemote() != "" {
keyType = "Remote"
key = kas.PublicKey.GetRemote()
}

t.Row(
kas.Id,
kas.Uri,
keyType,
key,
// TODO: render labels [https://github.com/opentdf/tructl/issues/73]
)
}
HandleSuccess(cmd, "", t, list)
},
}

kasRegistrysCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a new KAS registry entry, i.e. 'https://example.com'",
Run: func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
uri := flagHelper.GetRequiredString("uri")
local := flagHelper.GetOptionalString("public-key-local")
remote := flagHelper.GetOptionalString("public-key-remote")
metadataLabels := flagHelper.GetStringSlice("label", metadataLabels, cli.FlagHelperStringSliceOptions{Min: 0})

if local == "" && remote == "" {
e := fmt.Errorf("A public key is required. Please pass either a local or remote public key")
cli.ExitWithError("Issue with create flags 'public-key-local' and 'public-key-remote': ", e)
}

key := &kasregistry.PublicKey{}
keyType := "Local"
if local != "" {
if remote != "" {
e := fmt.Errorf("Only one public key is allowed. Please pass either a local or remote public key but not both")
cli.ExitWithError("Issue with create flags 'public-key-local' and 'public-key-remote': ", e)
}
key.PublicKey = &kasregistry.PublicKey_Local{Local: local}
} else {
keyType = "Remote"
key.PublicKey = &kasregistry.PublicKey_Remote{Remote: remote}
}

created, err := h.CreateKasRegistryEntry(
uri,
key,
getMetadataMutable(metadataLabels),
)
if err != nil {
cli.ExitWithError("Could not create KAS registry entry", err)
}

t := cli.NewTabular().
Rows([][]string{
{"Id", created.Id},
{"URI", created.Uri},
{"PublicKey Type", keyType},
{"PublicKey", local},
// TODO: render labels [https://github.com/opentdf/tructl/issues/73]
}...)

HandleSuccess(cmd, created.Id, t, created)
},
}

// Update one KAS registry entry
kasRegistryUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update a KAS registry entry",
Run: func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)

id := flagHelper.GetRequiredString("id")
uri := flagHelper.GetOptionalString("uri")
local := flagHelper.GetOptionalString("public-key-local")
remote := flagHelper.GetOptionalString("public-key-remote")
labels := flagHelper.GetStringSlice("label", metadataLabels, cli.FlagHelperStringSliceOptions{Min: 0})

if local == "" && remote == "" && len(labels) == 0 && uri == "" {
cli.ExitWithError("No values were passed to update. Please pass at least one value to update (E.G. 'uri', 'public-key-local', 'public-key-remote', 'label')", nil)
}

// TODO: should update of a type of key be a dangerous mutation or cause a need for confirmation in the CLI?
var pubKey *kasregistry.PublicKey
if local != "" && remote != "" {
e := fmt.Errorf("Only one public key is allowed. Please pass either a local or remote public key but not both")
cli.ExitWithError("Issue with update flags 'public-key-local' and 'public-key-remote': ", e)
} else if local != "" {
pubKey = &kasregistry.PublicKey{PublicKey: &kasregistry.PublicKey_Local{Local: local}}
} else if remote != "" {
pubKey = &kasregistry.PublicKey{PublicKey: &kasregistry.PublicKey_Remote{Remote: remote}}
}

updated, err := h.UpdateKasRegistryEntry(
id,
uri,
pubKey,
getMetadataMutable(labels),
getMetadataUpdateBehavior(),
)
if err != nil {
cli.ExitWithError("Could not update KAS registry entry", err)
}
t := cli.NewTabular().
Rows([][]string{
{"Id", id},
{"URI", uri},
// TODO: render labels [https://github.com/opentdf/tructl/issues/73]
}...)
HandleSuccess(cmd, id, t, updated)
},
}

kasRegistryDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a KAS registry entry by id",
Run: func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
id := flagHelper.GetRequiredString("id")

kas, err := h.GetKasRegistryEntry(id)
if err != nil {
errMsg := fmt.Sprintf("Could not find KAS registry entry (%s)", id)
cli.ExitWithNotFoundError(errMsg, err)
}

cli.ConfirmDelete("KAS Registry Entry: ", id)

if err := h.DeleteKasRegistryEntry(id); err != nil {
errMsg := fmt.Sprintf("Could not delete KAS registry entry (%s)", id)
cli.ExitWithError(errMsg, err)
}

t := cli.NewTabular().
Rows([][]string{
{"Id", kas.Id},
{"URI", kas.Uri},
}...)

HandleSuccess(cmd, kas.Id, t, kas)
},
}
)

func init() {
policyCmd.AddCommand(kasRegistryCmd)

kasRegistryCmd.AddCommand(kasRegistryGetCmd)
kasRegistryGetCmd.Flags().StringP("id", "i", "", "Id of the KAS registry entry")

kasRegistryCmd.AddCommand(kasRegistrysListCmd)
// TODO: active, inactive, any state querying [https://github.com/opentdf/tructl/issues/68]

kasRegistryCmd.AddCommand(kasRegistrysCreateCmd)
kasRegistrysCreateCmd.Flags().StringP("uri", "u", "", "The URI of the KAS registry entry")
kasRegistrysCreateCmd.Flags().StringP("public-key-local", "p", "", "A local public key for the registered Key Access Server (KAS)")
kasRegistrysCreateCmd.Flags().StringP("public-key-remote", "r", "", "A remote endpoint that provides a public key for the registered Key Access Server (KAS)")
injectLabelFlags(kasRegistrysCreateCmd, false)

kasRegistryCmd.AddCommand(kasRegistryUpdateCmd)
kasRegistryUpdateCmd.Flags().StringP("id", "i", "", "Id of the KAS registry entry")
kasRegistryUpdateCmd.Flags().StringP("uri", "u", "", "The URI of the KAS registry entry")
kasRegistryUpdateCmd.Flags().StringP("public-key-local", "p", "", "A local public key for the registered Key Access Server (KAS)")
kasRegistryUpdateCmd.Flags().StringP("public-key-remote", "r", "", "A remote endpoint that serves a public key for the registered Key Access Server (KAS)")
injectLabelFlags(kasRegistryUpdateCmd, true)

kasRegistryCmd.AddCommand(kasRegistryDeleteCmd)
kasRegistryDeleteCmd.Flags().StringP("id", "i", "", "Id of the KAS registry entry")
}

func init() {
rootCmd.AddCommand(kasRegistryCmd)
}
14 changes: 5 additions & 9 deletions cmd/policy-attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ used to define the access controls based on subject encodings and entity entitle
namespace := flagHelper.GetRequiredString("namespace")
metadataLabels := flagHelper.GetStringSlice("label", metadataLabels, cli.FlagHelperStringSliceOptions{Min: 0})

attr, err := h.CreateAttribute(name, rule, namespace, getMetadata(metadataLabels))
attr, err := h.CreateAttribute(name, rule, namespace, getMetadataMutable(metadataLabels))
if err != nil {
cli.ExitWithError("Could not create attribute", err)
}
Expand Down Expand Up @@ -106,7 +106,6 @@ used to define the access controls based on subject encodings and entity entitle
if err != nil {
errMsg := fmt.Sprintf("Could not find attribute (%s)", id)
cli.ExitWithNotFoundError(errMsg, err)
cli.ExitWithError(errMsg, err)
}

a := cli.GetSimpleAttribute(attr)
Expand All @@ -118,7 +117,7 @@ used to define the access controls based on subject encodings and entity entitle
{"Values", cli.CommaSeparated(a.Values)},
{"Namespace", a.Namespace},
}...)
HandleSuccess(cmd, a.Id, t, a)
HandleSuccess(cmd, a.Id, t, attr)
},
}

Expand Down Expand Up @@ -165,7 +164,6 @@ used to define the access controls based on subject encodings and entity entitle
if err != nil {
errMsg := fmt.Sprintf("Could not find attribute (%s)", id)
cli.ExitWithNotFoundError(errMsg, err)
cli.ExitWithError(errMsg, err)
}

cli.ConfirmDelete("attribute", attr.Name)
Expand All @@ -174,7 +172,6 @@ used to define the access controls based on subject encodings and entity entitle
if err != nil {
errMsg := fmt.Sprintf("Could not deactivate attribute (%s)", id)
cli.ExitWithNotFoundError(errMsg, err)
cli.ExitWithError(errMsg, err)
}

a := cli.GetSimpleAttribute(attr)
Expand All @@ -201,7 +198,7 @@ used to define the access controls based on subject encodings and entity entitle
id := flagHelper.GetRequiredString("id")
labels := flagHelper.GetStringSlice("label", metadataLabels, cli.FlagHelperStringSliceOptions{Min: 0})

if a, err := h.UpdateAttribute(id, getMetadata(labels), getMetadataUpdateBehavior()); err != nil {
if a, err := h.UpdateAttribute(id, getMetadataMutable(labels), getMetadataUpdateBehavior()); err != nil {
cli.ExitWithError("Could not update attribute", err)
} else {
HandleSuccess(cmd, id, nil, a)
Expand All @@ -220,7 +217,7 @@ func init() {
policy_attributesCreateCmd.Flags().StringSliceVarP(&attrValues, "values", "v", []string{}, "Values of the attribute")
policy_attributesCreateCmd.Flags().StringP("namespace", "s", "", "Namespace of the attribute")
policy_attributesCreateCmd.Flags().StringP("description", "d", "", "Description of the attribute")
policy_attributesCreateCmd.Flags().StringSliceVarP(&metadataLabels, "label", "l", []string{}, "Labels for the attribute")
injectLabelFlags(policy_attributesCreateCmd, false)

// Get an attribute
policy_attributesCmd.AddCommand(policy_attributeGetCmd)
Expand All @@ -232,8 +229,7 @@ func init() {
// Update an attribute
policy_attributesCmd.AddCommand(policy_attributeUpdateCmd)
policy_attributeUpdateCmd.Flags().StringP("id", "i", "", "Id of the attribute")
policy_attributeUpdateCmd.Flags().StringSliceVarP(&metadataLabels, "label", "l", []string{}, "Optional new metadata 'labels' in the format: key=value")
policy_attributeUpdateCmd.Flags().BoolVar(&forceReplaceMetadataLabels, "force-replace-labels", false, "Destructively replace entire set of existing metadata 'labels' with any provided to this command.")
injectLabelFlags(policy_attributeUpdateCmd, true)

// Delete an attribute
policy_attributesCmd.AddCommand(policy_attributesDeleteCmd)
Expand Down
Loading