From 540e5d0c1fe2f12d260ca6d226d22b7472cc00e4 Mon Sep 17 00:00:00 2001 From: Sean Trantalis Date: Mon, 10 Feb 2025 16:36:35 -0500 Subject: [PATCH 1/7] feat: new ns, def, value key mapping commands --- cmd/kas-public-keys.go | 540 ++++++++++++++++ cmd/kas-registry.go | 96 +-- cmd/policy-attributeNamespaces.go | 93 ++- cmd/policy-attributeValues.go | 98 ++- cmd/policy-attributes.go | 96 ++- docs/man/policy/attributes/keys/_index.md | 8 + docs/man/policy/attributes/keys/add.md | 24 + docs/man/policy/attributes/keys/list.md | 12 + docs/man/policy/attributes/keys/remove.md | 24 + .../attributes/namespaces/keys/_index.md | 8 + .../policy/attributes/namespaces/keys/add.md | 24 + .../policy/attributes/namespaces/keys/list.md | 12 + .../attributes/namespaces/keys/remove.md | 24 + .../policy/attributes/values/keys/_index.md | 8 + docs/man/policy/attributes/values/keys/add.md | 24 + .../man/policy/attributes/values/keys/list.md | 12 + .../policy/attributes/values/keys/remove.md | 24 + docs/man/policy/kas-registry/create.md | 5 +- .../policy/kas-registry/public-keys/_index.md | 9 + .../kas-registry/public-keys/activate.md | 20 + .../policy/kas-registry/public-keys/create.md | 35 ++ .../kas-registry/public-keys/deactivate.md | 20 + .../policy/kas-registry/public-keys/get.md | 20 + .../kas-registry/public-keys/list-mappings.md | 28 + .../policy/kas-registry/public-keys/list.md | 28 + .../public-keys/mappings/_index.md | 8 + .../kas-registry/public-keys/unsafe/_index.md | 19 + .../kas-registry/public-keys/unsafe/delete.md | 33 + .../policy/kas-registry/public-keys/update.md | 35 ++ e2e/attributes.bats | 127 +++- e2e/auth.bats | 2 +- e2e/encrypt-decrypt.bats | 2 +- e2e/helpers.bash | 214 +++++++ e2e/kas-grants.bats | 2 +- e2e/kas-registry.bats | 588 +++++++++++++++++- e2e/namespaces.bats | 78 ++- e2e/resource-mapping.bats | 2 +- e2e/subject-condition-sets.bats | 2 +- e2e/subject-mapping.bats | 2 +- go.mod | 4 +- go.sum | 2 - pkg/cli/confirm.go | 1 + pkg/cli/style.go | 110 ++-- pkg/cli/table.go | 3 +- pkg/handlers/attribute.go | 77 ++- pkg/handlers/attributeValues.go | 78 ++- pkg/handlers/kas-public-keys.go | 170 +++++ pkg/handlers/kas-registry.go | 19 +- pkg/handlers/namespaces.go | 78 ++- pkg/utils/validators.go | 15 + 50 files changed, 2773 insertions(+), 190 deletions(-) create mode 100644 cmd/kas-public-keys.go create mode 100644 docs/man/policy/attributes/keys/_index.md create mode 100644 docs/man/policy/attributes/keys/add.md create mode 100644 docs/man/policy/attributes/keys/list.md create mode 100644 docs/man/policy/attributes/keys/remove.md create mode 100644 docs/man/policy/attributes/namespaces/keys/_index.md create mode 100644 docs/man/policy/attributes/namespaces/keys/add.md create mode 100644 docs/man/policy/attributes/namespaces/keys/list.md create mode 100644 docs/man/policy/attributes/namespaces/keys/remove.md create mode 100644 docs/man/policy/attributes/values/keys/_index.md create mode 100644 docs/man/policy/attributes/values/keys/add.md create mode 100644 docs/man/policy/attributes/values/keys/list.md create mode 100644 docs/man/policy/attributes/values/keys/remove.md create mode 100644 docs/man/policy/kas-registry/public-keys/_index.md create mode 100644 docs/man/policy/kas-registry/public-keys/activate.md create mode 100644 docs/man/policy/kas-registry/public-keys/create.md create mode 100644 docs/man/policy/kas-registry/public-keys/deactivate.md create mode 100644 docs/man/policy/kas-registry/public-keys/get.md create mode 100644 docs/man/policy/kas-registry/public-keys/list-mappings.md create mode 100644 docs/man/policy/kas-registry/public-keys/list.md create mode 100644 docs/man/policy/kas-registry/public-keys/mappings/_index.md create mode 100644 docs/man/policy/kas-registry/public-keys/unsafe/_index.md create mode 100644 docs/man/policy/kas-registry/public-keys/unsafe/delete.md create mode 100644 docs/man/policy/kas-registry/public-keys/update.md create mode 100644 e2e/helpers.bash create mode 100644 pkg/handlers/kas-public-keys.go diff --git a/cmd/kas-public-keys.go b/cmd/kas-public-keys.go new file mode 100644 index 00000000..b2e454b2 --- /dev/null +++ b/cmd/kas-public-keys.go @@ -0,0 +1,540 @@ +package cmd + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "strings" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/term" + "github.com/evertras/bubble-table/table" + "github.com/opentdf/otdfctl/pkg/cli" + "github.com/opentdf/otdfctl/pkg/man" + "github.com/opentdf/platform/protocol/go/policy" + "github.com/opentdf/platform/protocol/go/policy/kasregistry" + "github.com/spf13/cobra" +) + +var policy_kasPublicKeyCmd = man.Docs.GetCommand("policy/kas-registry/public-keys") + +func policy_createPublicKey(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + kas := c.Flags.GetRequiredString("kas") + publicKey := c.Flags.GetRequiredString("key") + alg := c.Flags.GetRequiredString("algorithm") + kid := c.Flags.GetRequiredString("key-id") + metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) + + publicKey, err := parseAndFormatKey(publicKey) + if err != nil { + cli.ExitWithError("Failed to parse public key", err) + } + + created, err := h.CreatePublicKey(kas, publicKey, kid, alg, getMetadataMutable(metadataLabels)) + if err != nil { + cli.ExitWithError("Failed to create public key", err) + } + + rows := [][]string{ + {"Id", created.GetId()}, + {"Key ID", created.GetPublicKey().GetKid()}, + {"Algorithm", alg}, + {"Public Key", created.GetPublicKey().GetPem()}, + {"Was Mapped", created.GetWasMapped().String()}, + {"Active", created.GetIsActive().String()}, + } + + if mdRows := getMetadataRows(created.GetMetadata()); mdRows != nil { + rows = append(rows, mdRows...) + } + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, created.GetId(), t, created) +} + +func policy_updatePublicKey(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + id := c.Flags.GetRequiredID("id") + metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) + updated, err := h.UpdatePublicKey(id, getMetadataMutable(metadataLabels), getMetadataUpdateBehavior()) + if err != nil { + cli.ExitWithError("Failed to update public key", err) + } + + rows := [][]string{ + {"Id", updated.GetId()}, + {"Key ID", updated.GetPublicKey().GetKid()}, + } + + if mdRows := getMetadataRows(updated.GetMetadata()); mdRows != nil { + rows = append(rows, mdRows...) + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, updated.GetId(), t, updated) +} + +func policy_activePublicKey(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + id := c.Flags.GetRequiredID("id") + + err := h.ActivatePublicKey(id) + if err != nil { + cli.ExitWithError("Failed to active public key", err) + } + + HandleSuccess(cmd, id, table.Model{}, nil) +} + +func policy_deactivePublicKey(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + id := c.Flags.GetRequiredID("id") + + err := h.DeactivatePublicKey(id) + if err != nil { + cli.ExitWithError("Failed to deactivate public key", err) + } + + HandleSuccess(cmd, id, table.Model{}, nil) +} + +func policy_unsafeDeletePublicKey(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + id := c.Flags.GetRequiredID("id") + force := c.Flags.GetOptionalBool("force") + + pk, err := h.GetPublicKey(id) + if err != nil { + cli.ExitWithError(fmt.Sprintf("Failed to get public key with id %s", id), err) + } + + if !force { + cli.ConfirmTextInput(cli.ActionDelete, "public-key", cli.InputNameKeyID, pk.GetPublicKey().GetKid()) + } + + err = h.UnsafeDeletePublicKey(id) + if err != nil { + cli.ExitWithError("Failed to delete public key", err) + } + + rows := [][]string{ + {"Id", pk.GetId()}, + {"Key ID", pk.GetPublicKey().GetKid()}, + } + if mdRows := getMetadataRows(pk.GetMetadata()); mdRows != nil { + rows = append(rows, mdRows...) + } + t := cli.NewTabular(rows...) + HandleSuccess(cmd, pk.GetId(), t, pk) +} + +func policy_getPublicKey(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + id := c.Flags.GetRequiredID("id") + + key, err := h.GetPublicKey(id) + if err != nil { + cli.ExitWithError(fmt.Sprintf("Failed to get public key with id %s", id), err) + } + + alg, err := enumToAlg(key.GetPublicKey().GetAlg()) + if err != nil { + cli.ExitWithError("Failed to get algorithm", err) + } + + rows := [][]string{ + {"Id", key.GetId()}, + {"Was Mapped", fmt.Sprintf("%t", key.GetWasMapped().GetValue())}, + {"Active", fmt.Sprintf("%t", key.GetIsActive().GetValue())}, + {"KAS Name", key.GetKas().GetName()}, + {"KAS URI", key.GetKas().GetUri()}, + {"Key ID", key.GetPublicKey().GetKid()}, + {"Algorithm", alg}, + {"Public Key", key.GetPublicKey().GetPem()}, + } + + if mdRows := getMetadataRows(key.GetMetadata()); mdRows != nil { + rows = append(rows, mdRows...) + } + + cli.NewTable( + cli.NewUUIDColumn(), + ) + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, key.GetId(), t, key) +} + +func policy_listPublicKeys(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + kas := c.Flags.GetOptionalString("kas") + showPublicKey := c.Flags.GetOptionalBool("show-public-key") + offset := c.Flags.GetRequiredInt32("offset") + limit := c.Flags.GetRequiredInt32("limit") + + keys, page, err := h.ListPublicKeys(kas, offset, limit) + if err != nil { + cli.ExitWithError("Failed to list public keys", err) + } + + columns := []table.Column{ + table.NewFlexColumn("id", "ID", cli.FlexColumnWidthThree), + table.NewFlexColumn("is_active", "Active", cli.FlexColumnWidthTwo), + table.NewFlexColumn("was_mapped", "Was Mapped", cli.FlexColumnWidthTwo), + table.NewFlexColumn("kas_name", "KAS Name", cli.FlexColumnWidthThree), + table.NewFlexColumn("kas_uri", "KAS URI", cli.FlexColumnWidthThree), + table.NewFlexColumn("key_id", "Key ID", cli.FlexColumnWidthTwo), + table.NewFlexColumn("algorithm", "Algorithm", cli.FlexColumnWidthTwo), + } + + if showPublicKey { + columns = append(columns, table.NewFlexColumn("public_key", "Public Key", cli.FlexColumnWidthFour)) + } + + t := cli.NewTable(columns...) + + rows := []table.Row{} + for _, key := range keys { + alg, err := enumToAlg(key.GetPublicKey().GetAlg()) + if err != nil { + cli.ExitWithError("Failed to get algorithm", err) + } + + rowStyle := lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()) + + if key.GetIsActive().GetValue() { + rowStyle = rowStyle.Background(cli.ColorGreen.Background) + } else { + rowStyle = rowStyle.Background(cli.ColorRed.Background) + } + + rd := table.RowData{ + "id": key.GetId(), + "is_active": key.GetIsActive().GetValue(), + "was_mapped": key.GetWasMapped().GetValue(), + "kas_id": key.GetKas().GetId(), + "kas_name": key.GetKas().GetName(), + "kas_uri": key.GetKas().GetUri(), + "key_id": key.GetPublicKey().GetKid(), + "algorithm": alg, + "public_key": key.GetPublicKey().GetPem(), + } + + rows = append(rows, table.NewRow(rd).WithStyle(rowStyle)) + } + + t = t.WithRows(rows) + t = cli.WithListPaginationFooter(t, page) + + HandleSuccess(cmd, "", t, keys) +} + +func policy_listPublicKeyMappings(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + kas := c.Flags.GetOptionalString("kas") + pkID := c.Flags.GetOptionalID("public-key-id") + offset := c.Flags.GetRequiredInt32("offset") + limit := c.Flags.GetRequiredInt32("limit") + + mappings, page, err := h.ListPublicKeyMappings(kas, pkID, offset, limit) + if err != nil { + cli.ExitWithError("Failed to list public key mappings", err) + } + + t := cli.NewTable( + table.NewColumn("kas_name", "KAS Name", 20), + table.NewColumn("kas_uri", "KAS URI", 30), + table.NewColumn("key_count", "Key Count", 10), + table.NewFlexColumn("publicKeys", "Public Keys", 1), + ) + + rows := []table.Row{} + + termWidth, _, err := term.GetSize(os.Stdout.Fd()) + if err != nil { + termWidth = 80 + } + + for _, mapping := range mappings { + rows = append(rows, table.NewRow(table.RowData{ + "kas_name": mapping.GetKasName(), + "kas_uri": mapping.GetKasUri(), + "key_count": fmt.Sprintf("%d", len(mapping.GetPublicKeys())), + "publicKeys": createPublicKeysTable(mapping.GetPublicKeys(), termWidth), + }).WithStyle(lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()))) + } + + t = t.WithRows(rows) + t = cli.WithListPaginationFooter(t, page) + + HandleSuccess(cmd, "", t, mappings) +} + +func createPublicKeysTable(keys []*kasregistry.ListPublicKeyMappingResponse_PublicKey, termWidth int) string { + // Create columns for the nested table + columns := []table.Column{ + table.NewColumn("kid", "KID", 8), + table.NewColumn("algorithm", "Algorithm", 12), + table.NewColumn("active", "Active", 6), + table.NewFlexColumn("namespaces", "Namespaces", cli.FlexColumnWidthTwo), + table.NewFlexColumn("definitions", "Definitions", cli.FlexColumnWidthThree), + table.NewFlexColumn("values", "Values", cli.FlexColumnWidthFour), + } + + var rows []table.Row + + for _, pk := range keys { + alg, err := enumToAlg(pk.GetKey().GetPublicKey().GetAlg()) + if err != nil { + cli.ExitWithError("Failed to get algorithm", err) + } + + rowStyle := lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()) + + if pk.GetKey().GetIsActive().GetValue() { + rowStyle = rowStyle.Background(cli.ColorGreen.Background) + } else { + rowStyle = rowStyle.Background(cli.ColorRed.Background) + } + + rows = append(rows, table.NewRow(table.RowData{ + "kid": pk.GetKey().GetPublicKey().GetKid(), + "algorithm": alg, + "active": fmt.Sprintf("%v", pk.GetKey().GetIsActive().GetValue()), + "namespaces": formatAssociations(pk.GetNamespaces()), + "definitions": formatAssociations(pk.GetDefinitions()), + "values": formatAssociations(pk.GetValues()), + }).WithStyle(rowStyle)) + } + + minWidth := 80 // Set a minimum width for the nested table + tableWidth := int(float64(termWidth) * 0.75) + if tableWidth < minWidth { + tableWidth = minWidth + } + // Create nested table + nestedTable := table.New(columns). + WithRows(rows). + WithTargetWidth(int(float64(tableWidth) * 0.75)). + WithMultiline(true). + WithNoPagination(). + BorderRounded(). + WithBaseStyle(lipgloss.NewStyle().Align(lipgloss.Left)) + + // Convert the table to string and add some indentation + tableStr := nestedTable.View() + + // Add indentation to each line of the nested table + indentedLines := strings.Split(tableStr, "\n") + for i, line := range indentedLines { + indentedLines[i] = " " + line + } + return strings.Join(indentedLines, "\n") +} + +func formatAssociations(assocs []*kasregistry.ListPublicKeyMappingResponse_Association) string { + if len(assocs) == 0 { + return "-" + } + var fqns []string + for _, a := range assocs { + // remove https:// from the beginning of the URI + fqn, _ := strings.CutPrefix(a.GetFqn(), "https://") + fqns = append(fqns, fqn) + } + return strings.Join(fqns, "\n") +} + +func parseAndFormatKey(key string) (string, error) { + if key == "" { + return "", errors.New("key is required") + } + + // If the key contains a newline, replace it with the actual newline character + if strings.Contains(key, "\\n") { + return strings.ReplaceAll(key, "\\n", "\n"), nil + } + + // If the key is base64 encoded, decode it + if isValidBase64(key) { + decoded, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", err + } + return string(decoded), nil + } + + return key, nil +} + +func isValidBase64(s string) bool { + _, err := base64.StdEncoding.DecodeString(s) + return err == nil +} + +func enumToAlg(enum policy.KasPublicKeyAlgEnum) (string, error) { + switch enum { + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048: + return "rsa:2048", nil + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096: + return "rsa:4096", nil + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1: + return "ec:secp256r1", nil + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1: + return "ec:secp384r1", nil + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1: + return "ec:secp521r1", nil + default: + return "", errors.New("invalid enum algorithm") + } +} + +func init() { + createDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/create", + man.WithRun(policy_createPublicKey)) + createDoc.Flags().StringP( + createDoc.GetDocFlag("kas").Name, + createDoc.GetDocFlag("kas").Shorthand, + createDoc.GetDocFlag("kas").Default, + createDoc.GetDocFlag("kas").Description, + ) + createDoc.Flags().StringP( + createDoc.GetDocFlag("key").Name, + createDoc.GetDocFlag("key").Shorthand, + createDoc.GetDocFlag("key").Default, + createDoc.GetDocFlag("key").Description, + ) + createDoc.Flags().StringP( + createDoc.GetDocFlag("algorithm").Name, + createDoc.GetDocFlag("algorithm").Shorthand, + createDoc.GetDocFlag("algorithm").Default, + createDoc.GetDocFlag("algorithm").Description, + ) + createDoc.Flags().StringP( + createDoc.GetDocFlag("key-id").Name, + createDoc.GetDocFlag("key-id").Shorthand, + createDoc.GetDocFlag("key-id").Default, + createDoc.GetDocFlag("key-id").Description, + ) + injectLabelFlags(&createDoc.Command, false) + + updateDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/update", + man.WithRun(policy_updatePublicKey)) + updateDoc.Flags().StringP( + updateDoc.GetDocFlag("id").Name, + updateDoc.GetDocFlag("id").Shorthand, + updateDoc.GetDocFlag("id").Default, + updateDoc.GetDocFlag("id").Description, + ) + injectLabelFlags(&updateDoc.Command, true) + + activateDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/activate", + man.WithRun(policy_activePublicKey)) + activateDoc.Flags().StringP( + activateDoc.GetDocFlag("id").Name, + activateDoc.GetDocFlag("id").Shorthand, + activateDoc.GetDocFlag("id").Default, + activateDoc.GetDocFlag("id").Description, + ) + + deactivateDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/deactivate", + man.WithRun(policy_deactivePublicKey)) + deactivateDoc.Flags().StringP( + deactivateDoc.GetDocFlag("id").Name, + deactivateDoc.GetDocFlag("id").Shorthand, + deactivateDoc.GetDocFlag("id").Default, + deactivateDoc.GetDocFlag("id").Description, + ) + + getDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/get", + man.WithRun(policy_getPublicKey)) + getDoc.Flags().StringP( + getDoc.GetDocFlag("id").Name, + getDoc.GetDocFlag("id").Shorthand, + getDoc.GetDocFlag("id").Default, + getDoc.GetDocFlag("id").Description, + ) + + listDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/list", + man.WithRun(policy_listPublicKeys)) + listDoc.Flags().StringP( + listDoc.GetDocFlag("kas").Name, + listDoc.GetDocFlag("kas").Shorthand, + listDoc.GetDocFlag("kas").Default, + listDoc.GetDocFlag("kas").Description, + ) + listDoc.Flags().BoolP( + listDoc.GetDocFlag("show-public-key").Name, + listDoc.GetDocFlag("show-public-key").Shorthand, + listDoc.GetDocFlag("show-public-key").DefaultAsBool(), + listDoc.GetDocFlag("show-public-key").Description, + ) + injectListPaginationFlags(listDoc) + + listMappingsDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/list-mappings", + man.WithRun(policy_listPublicKeyMappings)) + listMappingsDoc.Flags().StringP( + listMappingsDoc.GetDocFlag("kas").Name, + listMappingsDoc.GetDocFlag("kas").Shorthand, + listMappingsDoc.GetDocFlag("kas").Default, + listMappingsDoc.GetDocFlag("kas").Description, + ) + listMappingsDoc.Flags().StringP( + listMappingsDoc.GetDocFlag("public-key-id").Name, + listMappingsDoc.GetDocFlag("public-key-id").Shorthand, + listMappingsDoc.GetDocFlag("public-key-id").Default, + listMappingsDoc.GetDocFlag("public-key-id").Description, + ) + injectListPaginationFlags(listMappingsDoc) + + unsafeDeleteDoc := man.Docs.GetCommand("policy/kas-registry/public-keys/unsafe/delete", + man.WithRun(policy_unsafeDeletePublicKey)) + unsafeDeleteDoc.Flags().StringP( + unsafeDeleteDoc.GetDocFlag("id").Name, + unsafeDeleteDoc.GetDocFlag("id").Shorthand, + unsafeDeleteDoc.GetDocFlag("id").Default, + unsafeDeleteDoc.GetDocFlag("id").Description, + ) + unsafeDeleteDoc.Flags().BoolP( + unsafeDeleteDoc.GetDocFlag("force").Name, + unsafeDeleteDoc.GetDocFlag("force").Shorthand, + unsafeDeleteDoc.GetDocFlag("force").DefaultAsBool(), + unsafeDeleteDoc.GetDocFlag("force").Description, + ) + + policy_kasPublicKeyUnsafeCmd := man.Docs.GetCommand("policy/kas-registry/public-keys/unsafe") + policy_kasPublicKeyUnsafeCmd.AddSubcommands(unsafeDeleteDoc) + + policy_kasPublicKeyCmd.AddCommand(&policy_kasPublicKeyUnsafeCmd.Command) + + policy_kasPublicKeyCmd.AddSubcommands(createDoc, updateDoc, getDoc, listDoc, listMappingsDoc, activateDoc, deactivateDoc) + policy_kasRegistryCmd.AddCommand(&policy_kasPublicKeyCmd.Command) +} diff --git a/cmd/kas-registry.go b/cmd/kas-registry.go index ee67e46d..fe72c620 100644 --- a/cmd/kas-registry.go +++ b/cmd/kas-registry.go @@ -1,9 +1,6 @@ package cmd import ( - "encoding/json" - "encoding/pem" - "errors" "fmt" "github.com/evertras/bubble-table/table" @@ -11,10 +8,9 @@ import ( "github.com/opentdf/otdfctl/pkg/man" "github.com/opentdf/platform/protocol/go/policy" "github.com/spf13/cobra" - "google.golang.org/protobuf/encoding/protojson" ) -var policy_kasRegistryCmd *cobra.Command +var policy_kasRegistryCmd = man.Docs.GetCommand("policy/kas-registry") func policy_getKeyAccessRegistry(cmd *cobra.Command, args []string) { c := cli.New(cmd, args) @@ -29,15 +25,9 @@ func policy_getKeyAccessRegistry(cmd *cobra.Command, args []string) { cli.ExitWithError(errMsg, err) } - key := &policy.PublicKey{} - key.PublicKey = &policy.PublicKey_Cached{Cached: kas.GetPublicKey().GetCached()} - if kas.GetPublicKey().GetRemote() != "" { - key.PublicKey = &policy.PublicKey_Remote{Remote: kas.GetPublicKey().GetRemote()} - } rows := [][]string{ {"Id", kas.GetId()}, {"URI", kas.GetUri()}, - {"PublicKey", kas.GetPublicKey().String()}, } name := kas.GetName() if name != "" { @@ -69,21 +59,13 @@ func policy_listKeyAccessRegistries(cmd *cobra.Command, args []string) { cli.NewUUIDColumn(), table.NewFlexColumn("uri", "URI", cli.FlexColumnWidthFour), table.NewFlexColumn("name", "Name", cli.FlexColumnWidthThree), - table.NewFlexColumn("pk", "PublicKey", cli.FlexColumnWidthFour), ) rows := []table.Row{} for _, kas := range list { - key := policy.PublicKey{} - key.PublicKey = &policy.PublicKey_Cached{Cached: kas.GetPublicKey().GetCached()} - if kas.GetPublicKey().GetRemote() != "" { - key.PublicKey = &policy.PublicKey_Remote{Remote: kas.GetPublicKey().GetRemote()} - } - rows = append(rows, table.NewRow(table.RowData{ "id": kas.GetId(), "uri": kas.GetUri(), "name": kas.GetName(), - "pk": kas.GetPublicKey().String(), })) } t = t.WithRows(rows) @@ -97,31 +79,12 @@ func policy_createKeyAccessRegistry(cmd *cobra.Command, args []string) { defer h.Close() uri := c.Flags.GetRequiredString("uri") - cachedJSON := c.Flags.GetOptionalString("public-keys") - remote := c.Flags.GetOptionalString("public-key-remote") name := c.Flags.GetOptionalString("name") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) - if cachedJSON == "" && remote == "" { - cli.ExitWithError("Empty flags 'public-keys' and 'public-key-remote'", errors.New("error: a public key is required")) - } - - key := new(policy.PublicKey) - if cachedJSON != "" { - if remote != "" { - cli.ExitWithError("Found values for both flags 'public-keys' and 'public-key-remote'", errors.New("error: only one public key is allowed")) - } - err := unmarshalKASPublicKey(cachedJSON, key) - if err != nil { - cli.ExitWithError(fmt.Sprintf("KAS registry key is invalid: '%s', see help for examples", cachedJSON), err) - } - } else { - key.PublicKey = &policy.PublicKey_Remote{Remote: remote} - } - created, err := h.CreateKasRegistryEntry( uri, - key, + &policy.PublicKey{}, name, getMetadataMutable(metadataLabels), ) @@ -132,7 +95,6 @@ func policy_createKeyAccessRegistry(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", created.GetId()}, {"URI", created.GetUri()}, - {"PublicKey", created.GetPublicKey().String()}, } if name != "" { rows = append(rows, []string{"Name", name}) @@ -153,34 +115,18 @@ func policy_updateKeyAccessRegistry(cmd *cobra.Command, args []string) { id := c.Flags.GetRequiredID("id") uri := c.Flags.GetOptionalString("uri") name := c.Flags.GetOptionalString("name") - cachedJSON := c.Flags.GetOptionalString("public-keys") - remote := c.Flags.GetOptionalString("public-key-remote") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) - allEmpty := cachedJSON == "" && remote == "" && len(metadataLabels) == 0 && uri == "" && name == "" + allEmpty := len(metadataLabels) == 0 && uri == "" && name == "" if allEmpty { cli.ExitWithError("No values were passed to update. Please pass at least one value to update (E.G. 'uri', 'name', 'public-keys', 'public-key-remote', 'label')", nil) } - pubKey := new(policy.PublicKey) - if cachedJSON != "" && remote != "" { - e := fmt.Errorf("only one public key is allowed. Please pass either a cached or remote public key but not both") - cli.ExitWithError("Issue with update flags 'public-keys' and 'public-key-remote': ", e) - } - if cachedJSON != "" { - err := unmarshalKASPublicKey(cachedJSON, pubKey) - if err != nil { - cli.ExitWithError(fmt.Sprintf("KAS registry key is invalid: '%s', see help for examples", cachedJSON), err) - } - } else if remote != "" { - pubKey.PublicKey = &policy.PublicKey_Remote{Remote: remote} - } - updated, err := h.UpdateKasRegistryEntry( id, uri, name, - pubKey, + &policy.PublicKey{}, getMetadataMutable(metadataLabels), getMetadataUpdateBehavior(), ) @@ -190,7 +136,6 @@ func policy_updateKeyAccessRegistry(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", id}, {"URI", updated.GetUri()}, - {"PublicKey", updated.GetPublicKey().String()}, } if updated.GetName() != "" { rows = append(rows, []string{"Name", updated.GetName()}) @@ -232,32 +177,6 @@ func policy_deleteKeyAccessRegistry(cmd *cobra.Command, args []string) { HandleSuccess(cmd, kas.GetId(), t, kas) } -// TODO: remove this when the data is structured -func unmarshalKASPublicKey(keyStr string, key *policy.PublicKey) error { - if !json.Valid([]byte(keyStr)) { - return errors.New("invalid JSON") - } - - if err := protojson.Unmarshal([]byte(keyStr), key); err != nil { - return errors.New("invalid shape") - } - - // Validate all PEMs - keyErrs := []error{} - for i, k := range key.GetCached().GetKeys() { - block, _ := pem.Decode([]byte(k.GetPem())) - if block == nil { - keyErrs = append(keyErrs, fmt.Errorf("error in key[%d] with KID \"%s\": PEM is invalid", i, k.GetKid())) - } - } - - if len(keyErrs) != 0 { - return errors.Join(keyErrs...) - } - - return nil -} - func init() { getDoc := man.Docs.GetCommand("policy/kas-registry/get", man.WithRun(policy_getKeyAccessRegistry), @@ -353,9 +272,6 @@ func init() { deleteDoc.GetDocFlag("force").Description, ) - doc := man.Docs.GetCommand("policy/kas-registry", - man.WithSubcommands(createDoc, getDoc, listDoc, updateDoc, deleteDoc), - ) - policy_kasRegistryCmd = &doc.Command - policyCmd.AddCommand(policy_kasRegistryCmd) + policy_kasRegistryCmd.AddSubcommands(createDoc, getDoc, listDoc, updateDoc, deleteDoc) + policyCmd.AddCommand(&policy_kasRegistryCmd.Command) } diff --git a/cmd/policy-attributeNamespaces.go b/cmd/policy-attributeNamespaces.go index 16dfde76..4132c8c9 100644 --- a/cmd/policy-attributeNamespaces.go +++ b/cmd/policy-attributeNamespaces.go @@ -12,6 +12,7 @@ import ( var ( policy_attributeNamespacesCmd = man.Docs.GetCommand("policy/attributes/namespaces") + policy_NamespaceKeysCmd = man.Docs.GetCommand("policy/attributes/namespaces/keys") forceUnsafe bool ) @@ -265,6 +266,53 @@ func policy_unsafeUpdateAttributeNamespace(cmd *cobra.Command, args []string) { HandleSuccess(cmd, ns.GetId(), t, ns) } +func policy_NamespaceKeysAdd(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + ns := c.Flags.GetRequiredString("namespace") + pkID := c.Flags.GetRequiredID("public-key-id") + + _, err := h.AddPublicKeyToNamespace(c.Context(), ns, pkID) + if err != nil { + cli.ExitWithError("Failed to add public key to namespace", err) + } + + rows := [][]string{ + {"Public Key Id", pkID}, + {"Namespace", ns}, + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, "Public key added to namespace", t, nil) +} + +func policy_NamespaceKeysRemove(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + ns := c.Flags.GetRequiredString("namespace") + pkID := c.Flags.GetRequiredID("public-key-id") + + _, err := h.RemovePublicKeyFromNamespace(c.Context(), ns, pkID) + if err != nil { + cli.ExitWithError("Failed to remove public key from namespace", err) + } + + rows := [][]string{ + {"Public Key Id", pkID}, + {"Namespace", ns}, + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, "Public key removed from namespace", t, nil) +} +func policy_NamespaceKeysListcmd(cmd *cobra.Command, args []string) {} + func init() { getCmd := man.Docs.GetCommand("policy/attributes/namespaces/get", man.WithRun(policy_getAttributeNamespace), @@ -367,6 +415,49 @@ func init() { ) unsafeCmd.AddSubcommands(deleteCmd, reactivateCmd, unsafeUpdateCmd) - policy_attributeNamespacesCmd.AddSubcommands(getCmd, listCmd, createDoc, updateCmd, deactivateCmd, unsafeCmd) + namespaceKeysAddDoc := man.Docs.GetCommand("policy/attributes/namespaces/keys/add", + man.WithRun(policy_NamespaceKeysAdd), + ) + namespaceKeysAddDoc.Flags().StringP( + namespaceKeysAddDoc.GetDocFlag("namespace").Name, + namespaceKeysAddDoc.GetDocFlag("namespace").Shorthand, + namespaceKeysAddDoc.GetDocFlag("namespace").Default, + namespaceKeysAddDoc.GetDocFlag("namespace").Description, + ) + namespaceKeysAddDoc.Flags().StringP( + namespaceKeysAddDoc.GetDocFlag("public-key-id").Name, + namespaceKeysAddDoc.GetDocFlag("public-key-id").Shorthand, + namespaceKeysAddDoc.GetDocFlag("public-key-id").Default, + namespaceKeysAddDoc.GetDocFlag("public-key-id").Description, + ) + + namespaceKeysRemoveDoc := man.Docs.GetCommand("policy/attributes/namespaces/keys/remove", + man.WithRun(policy_NamespaceKeysRemove), + ) + namespaceKeysRemoveDoc.Flags().StringP( + namespaceKeysRemoveDoc.GetDocFlag("namespace").Name, + namespaceKeysRemoveDoc.GetDocFlag("namespace").Shorthand, + namespaceKeysRemoveDoc.GetDocFlag("namespace").Default, + namespaceKeysRemoveDoc.GetDocFlag("namespace").Description, + ) + namespaceKeysRemoveDoc.Flags().StringP( + namespaceKeysRemoveDoc.GetDocFlag("public-key-id").Name, + namespaceKeysRemoveDoc.GetDocFlag("public-key-id").Shorthand, + namespaceKeysRemoveDoc.GetDocFlag("public-key-id").Default, + namespaceKeysRemoveDoc.GetDocFlag("public-key-id").Description, + ) + + namespaceKeysListDoc := man.Docs.GetCommand("policy/attributes/namespaces/keys/list", + man.WithRun(policy_NamespaceKeysListcmd), + ) + namespaceKeysListDoc.Flags().StringP( + namespaceKeysListDoc.GetDocFlag("namespace").Name, + namespaceKeysListDoc.GetDocFlag("namespace").Shorthand, + namespaceKeysListDoc.GetDocFlag("namespace").Default, + namespaceKeysListDoc.GetDocFlag("namespace").Description, + ) + + policy_NamespaceKeysCmd.AddSubcommands(namespaceKeysAddDoc, namespaceKeysRemoveDoc, namespaceKeysListDoc) + policy_attributeNamespacesCmd.AddSubcommands(getCmd, listCmd, createDoc, updateCmd, deactivateCmd, unsafeCmd, policy_NamespaceKeysCmd) policy_attributesCmd.AddCommand(&policy_attributeNamespacesCmd.Command) } diff --git a/cmd/policy-attributeValues.go b/cmd/policy-attributeValues.go index 633ede4c..ef06bf2e 100644 --- a/cmd/policy-attributeValues.go +++ b/cmd/policy-attributeValues.go @@ -10,7 +10,10 @@ import ( "github.com/spf13/cobra" ) -var policy_attributeValuesCmd *cobra.Command +var ( + policy_attributeValuesCmd *cobra.Command + policy_ValueKeysCmd = man.Docs.GetCommand("policy/attributes/values/keys") +) func policy_createAttributeValue(cmd *cobra.Command, args []string) { c := cli.New(cmd, args) @@ -226,6 +229,53 @@ func policy_unsafeDeleteAttributeValue(cmd *cobra.Command, args []string) { } } +func policy_ValueKeysAdd(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + val := c.Flags.GetRequiredString("value") + pkID := c.Flags.GetRequiredID("public-key-id") + + ak, err := h.AddPublicKeyToValue(c.Context(), val, pkID) + if err != nil { + cli.ExitWithError("Failed to add public key to value", err) + } + + rows := [][]string{ + {"Public Key Id", pkID}, + {"Attribute Value", val}, + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, "Public key added to value", t, ak) +} + +func policy_ValueKeysRemove(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + val := c.Flags.GetRequiredString("value") + pkID := c.Flags.GetRequiredID("public-key-id") + + _, err := h.RemovePublicKeyFromValue(c.Context(), val, pkID) + if err != nil { + cli.ExitWithError("Failed to remove public key from value", err) + } + + rows := [][]string{ + {"Public Key Id", pkID}, + {"Attribute Value", val}, + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, "Public key removed from value", t, nil) +} +func policy_ValueKeysList(cmd *cobra.Command, args []string) {} + func init() { createCmd := man.Docs.GetCommand("policy/attributes/values/create", man.WithRun(policy_createAttributeValue), @@ -335,9 +385,53 @@ func init() { unsafeCmd.GetDocFlag("force").Description, ) + valueKeysAddDoc := man.Docs.GetCommand("policy/attributes/values/keys/add", + man.WithRun(policy_ValueKeysAdd), + ) + valueKeysAddDoc.Flags().StringP( + valueKeysAddDoc.GetDocFlag("value").Name, + valueKeysAddDoc.GetDocFlag("value").Shorthand, + valueKeysAddDoc.GetDocFlag("value").Default, + valueKeysAddDoc.GetDocFlag("value").Description, + ) + valueKeysAddDoc.Flags().StringP( + valueKeysAddDoc.GetDocFlag("public-key-id").Name, + valueKeysAddDoc.GetDocFlag("public-key-id").Shorthand, + valueKeysAddDoc.GetDocFlag("public-key-id").Default, + valueKeysAddDoc.GetDocFlag("public-key-id").Description, + ) + + valueKeysRemoveDoc := man.Docs.GetCommand("policy/attributes/values/keys/remove", + man.WithRun(policy_ValueKeysRemove), + ) + valueKeysRemoveDoc.Flags().StringP( + valueKeysRemoveDoc.GetDocFlag("value").Name, + valueKeysRemoveDoc.GetDocFlag("value").Shorthand, + valueKeysRemoveDoc.GetDocFlag("value").Default, + valueKeysRemoveDoc.GetDocFlag("value").Description, + ) + valueKeysRemoveDoc.Flags().StringP( + valueKeysRemoveDoc.GetDocFlag("public-key-id").Name, + valueKeysRemoveDoc.GetDocFlag("public-key-id").Shorthand, + valueKeysRemoveDoc.GetDocFlag("public-key-id").Default, + valueKeysRemoveDoc.GetDocFlag("public-key-id").Description, + ) + + valueKeysListDoc := man.Docs.GetCommand("policy/attributes/values/keys/list", + man.WithRun(policy_ValueKeysList), + ) + valueKeysListDoc.Flags().StringP( + valueKeysListDoc.GetDocFlag("value").Name, + valueKeysListDoc.GetDocFlag("value").Shorthand, + valueKeysListDoc.GetDocFlag("value").Default, + valueKeysListDoc.GetDocFlag("value").Description, + ) + + policy_ValueKeysCmd.AddSubcommands(valueKeysAddDoc, valueKeysRemoveDoc, valueKeysListDoc) + unsafeCmd.AddSubcommands(unsafeReactivateCmd, unsafeDeleteCmd, unsafeUpdateCmd) doc := man.Docs.GetCommand("policy/attributes/values", - man.WithSubcommands(createCmd, getCmd, listCmd, updateCmd, deactivateCmd, unsafeCmd), + man.WithSubcommands(createCmd, getCmd, listCmd, updateCmd, deactivateCmd, unsafeCmd, policy_ValueKeysCmd), ) policy_attributeValuesCmd = &doc.Command policy_attributesCmd.AddCommand(policy_attributeValuesCmd) diff --git a/cmd/policy-attributes.go b/cmd/policy-attributes.go index 8a3dfc74..267c2910 100644 --- a/cmd/policy-attributes.go +++ b/cmd/policy-attributes.go @@ -16,7 +16,8 @@ var ( values []string valuesOrder []string - policy_attributesCmd = man.Docs.GetCommand("policy/attributes") + policy_attributesCmd = man.Docs.GetCommand("policy/attributes") + policy_DefinitionKeysCmd = man.Docs.GetCommand("policy/attributes/keys") ) func policy_createAttribute(cmd *cobra.Command, args []string) { @@ -300,6 +301,53 @@ func policy_unsafeDeleteAttribute(cmd *cobra.Command, args []string) { } } +func policy_DefinitionKeysAdd(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + def := c.Flags.GetRequiredString("definition") + pkID := c.Flags.GetRequiredID("public-key-id") + + ak, err := h.AddPublicKeyToDefinition(c.Context(), def, pkID) + if err != nil { + cli.ExitWithError("Failed to add public key to definition", err) + } + + rows := [][]string{ + {"Public Key Id", pkID}, + {"Attribute Definition", def}, + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, "Public key added to definition", t, ak) +} + +func policy_DefinitionKeysRemove(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + def := c.Flags.GetRequiredString("definition") + pkID := c.Flags.GetRequiredID("public-key-id") + + _, err := h.RemovePublicKeyFromDefinition(c.Context(), def, pkID) + if err != nil { + cli.ExitWithError("Failed to remove public key from definition", err) + } + + rows := [][]string{ + {"Public Key Id", pkID}, + {"Attribute Definition", def}, + } + + t := cli.NewTabular(rows...) + + HandleSuccess(cmd, "Public key removed from definition", t, nil) +} +func policy_DefinitionKeysList(cmd *cobra.Command, args []string) {} + func init() { // Create an attribute createDoc := man.Docs.GetCommand("policy/attributes/create", @@ -438,7 +486,51 @@ func init() { unsafeUpdateCmd.GetDocFlag("values-order").Description, ) + definitionKeysAddDoc := man.Docs.GetCommand("policy/attributes/keys/add", + man.WithRun(policy_DefinitionKeysAdd), + ) + definitionKeysAddDoc.Flags().StringP( + definitionKeysAddDoc.GetDocFlag("definition").Name, + definitionKeysAddDoc.GetDocFlag("definition").Shorthand, + definitionKeysAddDoc.GetDocFlag("definition").Default, + definitionKeysAddDoc.GetDocFlag("definition").Description, + ) + definitionKeysAddDoc.Flags().StringP( + definitionKeysAddDoc.GetDocFlag("public-key-id").Name, + definitionKeysAddDoc.GetDocFlag("public-key-id").Shorthand, + definitionKeysAddDoc.GetDocFlag("public-key-id").Default, + definitionKeysAddDoc.GetDocFlag("public-key-id").Description, + ) + + definitionKeysRemoveDoc := man.Docs.GetCommand("policy/attributes/keys/remove", + man.WithRun(policy_DefinitionKeysRemove), + ) + definitionKeysRemoveDoc.Flags().StringP( + definitionKeysRemoveDoc.GetDocFlag("definition").Name, + definitionKeysRemoveDoc.GetDocFlag("definition").Shorthand, + definitionKeysRemoveDoc.GetDocFlag("definition").Default, + definitionKeysRemoveDoc.GetDocFlag("definition").Description, + ) + definitionKeysRemoveDoc.Flags().StringP( + definitionKeysRemoveDoc.GetDocFlag("public-key-id").Name, + definitionKeysRemoveDoc.GetDocFlag("public-key-id").Shorthand, + definitionKeysRemoveDoc.GetDocFlag("public-key-id").Default, + definitionKeysRemoveDoc.GetDocFlag("public-key-id").Description, + ) + + definitionKeysListDoc := man.Docs.GetCommand("policy/attributes/keys/list", + man.WithRun(policy_DefinitionKeysList), + ) + definitionKeysListDoc.Flags().StringP( + definitionKeysListDoc.GetDocFlag("definition").Name, + definitionKeysListDoc.GetDocFlag("definition").Shorthand, + definitionKeysListDoc.GetDocFlag("definition").Default, + definitionKeysListDoc.GetDocFlag("definition").Description, + ) + + policy_DefinitionKeysCmd.AddSubcommands(definitionKeysAddDoc, definitionKeysRemoveDoc, definitionKeysListDoc) + unsafeCmd.AddSubcommands(reactivateCmd, deleteCmd, unsafeUpdateCmd) - policy_attributesCmd.AddSubcommands(createDoc, getDoc, listDoc, updateDoc, deactivateDoc, unsafeCmd) + policy_attributesCmd.AddSubcommands(createDoc, getDoc, listDoc, updateDoc, deactivateDoc, unsafeCmd, policy_DefinitionKeysCmd) policyCmd.AddCommand(&policy_attributesCmd.Command) } diff --git a/docs/man/policy/attributes/keys/_index.md b/docs/man/policy/attributes/keys/_index.md new file mode 100644 index 00000000..b7bbb872 --- /dev/null +++ b/docs/man/policy/attributes/keys/_index.md @@ -0,0 +1,8 @@ +--- +title: Manage Attribute Keys +command: + name: keys + aliases: + - k + - key +--- \ No newline at end of file diff --git a/docs/man/policy/attributes/keys/add.md b/docs/man/policy/attributes/keys/add.md new file mode 100644 index 00000000..64b9bfbf --- /dev/null +++ b/docs/man/policy/attributes/keys/add.md @@ -0,0 +1,24 @@ +--- +title: Add a Public Key Mapping +command: + name: add + aliases: + - a + flags: + - name: public-key-id + shorthand: i + description: ID of the Public Key + required: true + - name: definition + shorthand: d + description: ID or FQN of the Attribute Definition + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/attributes/keys/list.md b/docs/man/policy/attributes/keys/list.md new file mode 100644 index 00000000..e944b6f6 --- /dev/null +++ b/docs/man/policy/attributes/keys/list.md @@ -0,0 +1,12 @@ +--- +title: List Attribute Keys +command: + name: list + aliases: + - l + flags: + - name: definition + shorthand: d + description: ID or FQN of the Attribute Definition + required: true +--- \ No newline at end of file diff --git a/docs/man/policy/attributes/keys/remove.md b/docs/man/policy/attributes/keys/remove.md new file mode 100644 index 00000000..e0ee6a62 --- /dev/null +++ b/docs/man/policy/attributes/keys/remove.md @@ -0,0 +1,24 @@ +--- +title: Remove a Public Key Mapping +command: + name: remove + aliases: + - r + flags: + - name: public-key-id + shorthand: i + description: ID of the Public Key + required: true + - name: definition + shorthand: d + description: ID or FQN of the Attribute Definition + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/attributes/namespaces/keys/_index.md b/docs/man/policy/attributes/namespaces/keys/_index.md new file mode 100644 index 00000000..0a3de089 --- /dev/null +++ b/docs/man/policy/attributes/namespaces/keys/_index.md @@ -0,0 +1,8 @@ +--- +title: Manage Namespace Keys +command: + name: keys + aliases: + - k + - key +--- \ No newline at end of file diff --git a/docs/man/policy/attributes/namespaces/keys/add.md b/docs/man/policy/attributes/namespaces/keys/add.md new file mode 100644 index 00000000..e627d564 --- /dev/null +++ b/docs/man/policy/attributes/namespaces/keys/add.md @@ -0,0 +1,24 @@ +--- +title: Add a Public Key Mapping +command: + name: add + aliases: + - a + flags: + - name: public-key-id + shorthand: i + description: ID of the Public Key + required: true + - name: namespace + shorthand: n + description: ID or FQN of the Attribute Namespace + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/attributes/namespaces/keys/list.md b/docs/man/policy/attributes/namespaces/keys/list.md new file mode 100644 index 00000000..260a226c --- /dev/null +++ b/docs/man/policy/attributes/namespaces/keys/list.md @@ -0,0 +1,12 @@ +--- +title: List Namespace Keys +command: + name: list + aliases: + - l + flags: + - name: namespace + shorthand: n + description: ID or FQN of the Attribute Namespace + required: true +--- \ No newline at end of file diff --git a/docs/man/policy/attributes/namespaces/keys/remove.md b/docs/man/policy/attributes/namespaces/keys/remove.md new file mode 100644 index 00000000..b5f6d362 --- /dev/null +++ b/docs/man/policy/attributes/namespaces/keys/remove.md @@ -0,0 +1,24 @@ +--- +title: Remove a Public Key Mapping +command: + name: remove + aliases: + - r + flags: + - name: public-key-id + shorthand: i + description: ID of the Public Key + required: true + - name: namespace + shorthand: d + description: ID or FQN of the Attribute Namespace + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/attributes/values/keys/_index.md b/docs/man/policy/attributes/values/keys/_index.md new file mode 100644 index 00000000..0ec3bbcc --- /dev/null +++ b/docs/man/policy/attributes/values/keys/_index.md @@ -0,0 +1,8 @@ +--- +title: Manage Value Keys +command: + name: keys + aliases: + - k + - key +--- \ No newline at end of file diff --git a/docs/man/policy/attributes/values/keys/add.md b/docs/man/policy/attributes/values/keys/add.md new file mode 100644 index 00000000..591c1597 --- /dev/null +++ b/docs/man/policy/attributes/values/keys/add.md @@ -0,0 +1,24 @@ +--- +title: Add a Public Key +command: + name: add + aliases: + - a + flags: + - name: public-key-id + shorthand: i + description: ID of the Public Key + required: true + - name: value + shorthand: v + description: ID or FQN of the Attribute Value + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/attributes/values/keys/list.md b/docs/man/policy/attributes/values/keys/list.md new file mode 100644 index 00000000..ff4c6837 --- /dev/null +++ b/docs/man/policy/attributes/values/keys/list.md @@ -0,0 +1,12 @@ +--- +title: List Key Mappings +command: + name: list + aliases: + - l + flags: + - name: value + shorthand: v + description: ID or FQN of the Attribute Value + required: true +--- \ No newline at end of file diff --git a/docs/man/policy/attributes/values/keys/remove.md b/docs/man/policy/attributes/values/keys/remove.md new file mode 100644 index 00000000..cdcc1be0 --- /dev/null +++ b/docs/man/policy/attributes/values/keys/remove.md @@ -0,0 +1,24 @@ +--- +title: Remove a Public Key +command: + name: remove + aliases: + - r + flags: + - name: public-key-id + shorthand: i + description: ID of the Public Key + required: true + - name: value + shorthand: v + description: ID or FQN of the Attribute Value + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/kas-registry/create.md b/docs/man/policy/kas-registry/create.md index 02d1735a..7f9bc82e 100644 --- a/docs/man/policy/kas-registry/create.md +++ b/docs/man/policy/kas-registry/create.md @@ -13,11 +13,10 @@ command: required: true - name: public-keys shorthand: c - description: One or more public keys saved for the KAS + description: (Deprecated) One or more public keys saved for the KAS - name: public-key-remote shorthand: r - description: Remote URI where the public key can be retrieved for the KAS - - name: label + description: (Deprecated) Remote URI where the public key can be retrieved for the KAS - name: name shorthand: n description: Optional name of the registered KAS (must be unique within policy) diff --git a/docs/man/policy/kas-registry/public-keys/_index.md b/docs/man/policy/kas-registry/public-keys/_index.md new file mode 100644 index 00000000..5053325d --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/_index.md @@ -0,0 +1,9 @@ +--- +title: Manage Key Access Server Public Keys +command: + name: public-key + aliases: + - pk +--- + + diff --git a/docs/man/policy/kas-registry/public-keys/activate.md b/docs/man/policy/kas-registry/public-keys/activate.md new file mode 100644 index 00000000..0b2e62e1 --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/activate.md @@ -0,0 +1,20 @@ +--- +title: Activate a Public Key +command: + name: activate + aliases: + - a + flags: + - name: id + shorthand: i + description: ID of the Public Key + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/kas-registry/public-keys/create.md b/docs/man/policy/kas-registry/public-keys/create.md new file mode 100644 index 00000000..166f35f7 --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/create.md @@ -0,0 +1,35 @@ +--- +title: Add a Public Key to a Key Access Server +command: + name: create + aliases: + - add + flags: + - name: kas + shorthand: k + description: Key Access Server ID, Name or URI. + required: true + - name: key + shorthand: p + description: Public key to add to the KAS. Must be in PEM format. Can be base64 encoded or plain text. + required: true + - name: key-id + shorthand: i + description: ID of the public key. + - name: algorithm + shorthand: a + description: Algorithm of the public key. (rsa:2048, rsa:4096, ec:secp256r1, ec:secp384r1, ec:secp521r1) + - name: label + description: "Optional metadata 'labels' in the format: key=value" + shorthand: l + default: '' + +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key add --kas-id 1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q=\n-----END CERTIFICATE-----\n" --algorithm rsa:2048 +``` diff --git a/docs/man/policy/kas-registry/public-keys/deactivate.md b/docs/man/policy/kas-registry/public-keys/deactivate.md new file mode 100644 index 00000000..07f611e8 --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/deactivate.md @@ -0,0 +1,20 @@ +--- +title: Deactivate a Public Key +command: + name: deactivate + aliases: + - d + flags: + - name: id + shorthand: i + description: ID of the Public Key + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/kas-registry/public-keys/get.md b/docs/man/policy/kas-registry/public-keys/get.md new file mode 100644 index 00000000..e97c20c6 --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/get.md @@ -0,0 +1,20 @@ +--- +title: Get a Public Key +command: + name: get + aliases: + - g + flags: + - name: id + shorthand: i + description: ID of the Public Key + required: true +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +``` diff --git a/docs/man/policy/kas-registry/public-keys/list-mappings.md b/docs/man/policy/kas-registry/public-keys/list-mappings.md new file mode 100644 index 00000000..6286b46d --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/list-mappings.md @@ -0,0 +1,28 @@ +--- +title: List Public Key Mappings +command: + name: list-mappings + aliases: + - lm + flags: + - name: kas + shorthand: k + description: Key Access Server ID, Name or URI. + - name: public-key-id + shorthand: p + description: Public Key ID + - name: limit + shorthand: l + description: Limit retrieved count + - name: offset + shorthand: o + description: Offset (page) quantity from start of the list +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry list +``` diff --git a/docs/man/policy/kas-registry/public-keys/list.md b/docs/man/policy/kas-registry/public-keys/list.md new file mode 100644 index 00000000..27737c53 --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/list.md @@ -0,0 +1,28 @@ +--- +title: List Public Keys +command: + name: list + aliases: + - l + flags: + - name: kas + shorthand: k + description: Key Access Server ID, Name or URI. + - name: show-public-key + description: Show the public key + default: false + - name: limit + shorthand: l + description: Limit retrieved count + - name: offset + shorthand: o + description: Offset (page) quantity from start of the list +--- + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry public-key list +``` diff --git a/docs/man/policy/kas-registry/public-keys/mappings/_index.md b/docs/man/policy/kas-registry/public-keys/mappings/_index.md new file mode 100644 index 00000000..e1fd945c --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/mappings/_index.md @@ -0,0 +1,8 @@ +--- +title: Manage Public Key Mappings +command: + name: mappings + aliases: + - mp + - map +--- \ No newline at end of file diff --git a/docs/man/policy/kas-registry/public-keys/unsafe/_index.md b/docs/man/policy/kas-registry/public-keys/unsafe/_index.md new file mode 100644 index 00000000..21269c1a --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/unsafe/_index.md @@ -0,0 +1,19 @@ +--- +title: Unsafe changes to Key Access Server Public Keys +command: + name: unsafe + flags: + - name: force + description: Force unsafe change without confirmation + required: false +--- + +Unsafe changes are dangerous mutations to Policy that can significantly change access behavior around existing attributes +and entitlement. + +Depending on the unsafe change introduced and already existing TDFs, TDFs might become inaccessible that were previously +accessible or vice versa. + +Make sure you know what you are doing. + +For more general information about attributes, see the `attributes` subcommand. diff --git a/docs/man/policy/kas-registry/public-keys/unsafe/delete.md b/docs/man/policy/kas-registry/public-keys/unsafe/delete.md new file mode 100644 index 00000000..cb4ac612 --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/unsafe/delete.md @@ -0,0 +1,33 @@ +--- +title: Delete a Key Access Server Public Key +command: + name: delete + aliases: + - d + - del + - remove + - rm + flags: + - name: id + shorthand: i + description: ID of the Key Access Server Public Key + required: true + - name: force + description: Force deletion without interactive confirmation (dangerous) +--- + +Removes knowledge of a KAS (registration) from a platform's policy. + +If resource data has been TDFd utilizing key splits from the registered KAS, deletion from +the registry (and therefore any associated grants) may prevent decryption depending on the +type of grants and relevant key splits. + +Make sure you know what you are doing. + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry delete --id 3c39618a-cd8c-48cf-a60c-e8a2f4be4dd5 +``` diff --git a/docs/man/policy/kas-registry/public-keys/update.md b/docs/man/policy/kas-registry/public-keys/update.md new file mode 100644 index 00000000..1dea392b --- /dev/null +++ b/docs/man/policy/kas-registry/public-keys/update.md @@ -0,0 +1,35 @@ +--- +title: Update Public Key Metadata +command: + name: update + aliases: + - u + flags: + - name: id + shorthand: i + description: ID of the Public Key + required: true + - name: label + description: "Optional metadata 'labels' in the format: key=value" + shorthand: l + default: '' + - name: force-replace-labels + description: Destructively replace entire set of existing metadata 'labels' with any provided to this command + default: false +--- + +Update the `uri`, `metadata`, or key material (remote/cached) for a KAS registered to the platform. + +If resource data has been TDFd utilizing key splits from the registered KAS, deletion from +the registry (and therefore any associated grants) may prevent decryption depending on the +type of grants and relevant key splits. + +Make sure you know what you are doing. + +For more information about registration of Key Access Servers, see the manual for `kas-registry`. + +## Example + +```shell +otdfctl policy kas-registry update --id 3c39618a-cd8c-48cf-a60c-e8a2f4be4dd5 --name example-kas2-newname --public-key-remote "https://example.com/kas2/new_public_key" +``` diff --git a/e2e/attributes.bats b/e2e/attributes.bats index 7ad3fbc7..69945e29 100755 --- a/e2e/attributes.bats +++ b/e2e/attributes.bats @@ -1,11 +1,12 @@ #!/usr/bin/env bats +load "./helpers.bash" # Tests for attributes setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" # Create the namespace to be used by other tests @@ -15,8 +16,7 @@ setup_file() { # always create a randomly named attribute setup() { - load "${BATS_LIB_PATH}/bats-support/load.bash" - load "${BATS_LIB_PATH}/bats-assert/load.bash" + setup_helper # invoke binary with credentials run_otdfctl_attr () { @@ -30,6 +30,8 @@ setup() { # always unsafely delete the created attribute teardown() { ./otdfctl $HOST $WITH_CREDS policy attributes unsafe delete --force --id "$ATTR_ID" + + cleanup_helper } teardown_file() { @@ -205,4 +207,123 @@ teardown_file() { assert_success [ "$(echo "$output" | jq -r '.values[0].value')" = "val2" ] [ "$(echo "$output" | jq -r '.values[1].value')" = "val1" ] +} + +@test "add_remove_key_to_definition" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Add the key to the attribute definition + log_info "Running ${run_otdfctl_attr} keys add --definition $ATTR_ID --public-key-id $KID" + run_otdfctl_attr keys add --definition "$ATTR_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was added to the attribute definition + log_info "Running ${run_otdfctl_attr} get --id $ATTR_ID" + run_otdfctl_attr get --id "$ATTR_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -r '.keys[].id' | while read -r id; do + log_debug "Checking PK ID: $id against $PUBLIC_KEY_ID" + [ "$id" = "$PUBLIC_KEY_ID" ] || fail "KAS ID does not match" + done + + # Remove the key from the attribute definition + log_info "Running ${run_otdfctl_attr} keys remove --definition $ATTR_ID --public-key-id $PUBLIC_KEY_ID" + run_otdfctl_attr keys remove --definition "$ATTR_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was removed from the attribute definition + log_info "Running ${run_otdfctl_attr} get --id $ATTR_ID" + run_otdfctl_attr get --id "$ATTR_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -e 'has("keys") | not' || fail "KAS ID still present" +} + +@test "add_remove_key_to_value" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Add value to the attribute definition + log_info "Running ${run_otdfctl_attr} values create --attribute-id $ATTR_ID --value val1" + run_otdfctl_attr values create --attribute-id "$ATTR_ID" --value val1 --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + VALUE_ID=$(echo "$output" | jq -r '.id') + + # Add the key to the attribute value + log_info "Running ${run_otdfctl_attr} values keys add --value $VALUE_ID --public-key-id $KID" + run_otdfctl_attr values keys add --value "$VALUE_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was added to the attribute value + log_info "Running ${run_otdfctl_attr} values get --id $VALUE_ID" + run_otdfctl_attr values get --id "$VALUE_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -r '.keys[].id' | while read -r id; do + log_debug "Checking PK ID: $id against $PUBLIC_KEY_ID" + [ "$id" = "$PUBLIC_KEY_ID" ] || fail "KAS ID does not match" + done + + # Remove the key from the attribute value + log_info "Running ${run_otdfctl_attr} keys remove --value $VALUE_ID --public-key-id $PUBLIC_KEY_ID" + run_otdfctl_attr values keys remove --value "$VALUE_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was removed from the attribute value + log_info "Running ${run_otdfctl_attr} values get --id $VALUE_ID" + run_otdfctl_attr values get --id "$VALUE_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -e 'has("keys") | not' || fail "KAS ID still present" } \ No newline at end of file diff --git a/e2e/auth.bats b/e2e/auth.bats index 486bbabc..315e2c6c 100755 --- a/e2e/auth.bats +++ b/e2e/auth.bats @@ -5,7 +5,7 @@ setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" } setup() { diff --git a/e2e/encrypt-decrypt.bats b/e2e/encrypt-decrypt.bats index 9861b593..3bc069ad 100755 --- a/e2e/encrypt-decrypt.bats +++ b/e2e/encrypt-decrypt.bats @@ -7,7 +7,7 @@ setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > $CREDSFILE export WITH_CREDS="--with-client-creds-file $CREDSFILE" export DEBUG_LEVEL="--log-level debug" - export HOST=http://localhost:8080 + export HOST="${HOST:---host http://localhost:8080}" export INFILE_GO_MOD=go.mod export OUTFILE_GO_MOD=go.mod.tdf diff --git a/e2e/helpers.bash b/e2e/helpers.bash new file mode 100644 index 00000000..8c158a16 --- /dev/null +++ b/e2e/helpers.bash @@ -0,0 +1,214 @@ +# helpers.bash +#!/usr/bin/env bash + +# OTDFCTL Helper Functions +run_otdfctl_kasr () { + run sh -c "./otdfctl policy kas-registry $HOST $WITH_CREDS $*" +} + +create_kas () { + log_debug "Creating KAS... $1 $2" + + run_otdfctl_kasr create --uri "$1" -n "$2" --json + + log_debug "Created KAS: $output" # Debug log: the output of the create command + + KAS_ID=$(echo "$output" | jq -r '.id') +} + +create_public_key() { + local kas="$1" + local key_id="$2" + local algorithm="$3" + local key_content + local label_args="$4" + + log_debug "Creating public key..." + + # Select the appropriate key generation function based on the algorithm + case "$algorithm" in + "$RSA_2048_ALG") + eval "$(gen_rsa_2048)" + key_content="$RSA_2048_PUBLIC_KEY" + ;; + "$RSA_4096_ALG") + eval "$(gen_rsa_4096)" + key_content="$RSA_4096_PUBLIC_KEY" + ;; + "$EC_256_ALG") + eval "$(gen_ec256)" + key_content="$EC_256_PUBLIC_KEY" + ;; + "$EC_384_ALG") + eval "$(gen_ec384)" + key_content="$EC_384_PUBLIC_KEY" + ;; + "$EC_521_ALG") + eval "$(gen_ec521)" + key_content="$EC_521_PUBLIC_KEY" + ;; + *) + log_error "Unsupported algorithm: $algorithm" + return 1 + ;; + esac + + # Verify key content is not empty + if [ -z "$key_content" ]; then + log_info "Empty key content for algorithm: $algorithm" + return 1 + fi + + # Base64 encode the key content + key_content=$(echo "$key_content" | base64 -w 0) + + log_debug "Running ${run_otdfctl_kasr} public-key create --kas $kas --key "$key_content" --key-id $key_id --algorithm $algorithm $label_args --json" + run_otdfctl_kasr public-key create \ + --kas "$kas" \ + --key "$key_content" \ + --key-id "$key_id" \ + --algorithm "$algorithm" \ + $label_args \ + --json + + if [ -z "$output" ]; then + log_info "Failed to create public key" + return 1 + fi + + log_debug "Created public key: $output" + + PUBLIC_KEY_ID=$(echo "$output" | jq -r '.id') + PUBLIC_KEY_IDS+=("$PUBLIC_KEY_ID") +} + +# Setup Helper +setup_helper(){ + load "${BATS_LIB_PATH}/bats-support/load.bash" + load "${BATS_LIB_PATH}/bats-assert/load.bash" + + # Initialize IDs to empty strings in case creation fails + KAS_ID="" + PUBLIC_KEY_ID="" + PUBLIC_KEY_IDS=() # Initialize an empty array + + KAS_URI="https://testing-public-key.io" + KAS_NAME="public-key-kas" + + RSA_2048_ALG="rsa:2048" + RSA_4096_ALG="rsa:4096" + EC_256_ALG="ec:secp256r1" + EC_384_ALG="ec:secp384r1" + EC_521_ALG="ec:secp521r1" +} + +# Cleanup Helper +cleanup_helper(){ + # Iterate over the array of public key IDs and delete them + for PUBLIC_KEY_ID in "${PUBLIC_KEY_IDS[@]}"; do + if [ -n "$PUBLIC_KEY_ID" ]; then + log_debug "Running ${run_otdfctl_kasr} public-key unsafe delete --id $PUBLIC_KEY_ID --force --json" + run_otdfctl_kasr public-key unsafe delete --id "$PUBLIC_KEY_ID" --force --json + log_debug "$output" + if [ $? -ne 0 ]; then + log_info "Error: Failed to delete public key with ID: $PUBLIC_KEY_ID" + fi + log_debug "Deleted public key with ID: $PUBLIC_KEY_ID" + fi + done + if [ -n "$KAS_ID" ]; then + log_debug "Running ${run_otdfctl_kasr} delete --id $KAS_ID --force --json" + run_otdfctl_kasr delete --id "$KAS_ID" --force --json + log_debug "$output" + if [ $? -ne 0 ]; then + log_info "Error: Failed to delete KAS registry with ID: $KAS_ID" + fi + log_debug "Deleted KAS registry with ID: $KAS_ID" + fi +} + +# Helper function for debug logging +log_debug() { + if [[ "${BATS_DEBUG:-0}" == "1" ]]; then + echo "DEBUG($BATS_TEST_NAME): $1" >&3 + fi +} + +# Helper function for info logging +log_info() { + echo "INFO($BATS_TEST_NAME): $1" >&3 +} + +# Helper function to generate a rsa 2048 key pair +gen_rsa_2048() { + log_debug "Generating RSA 2048 key pair" + local private_key public_key + + # Generate private key + private_key=$(openssl genrsa 2048) + + # Extract public key + public_key=$(echo "$private_key" | openssl rsa -pubout) + + # Output using proper escaping + printf 'export RSA_2048_PUBLIC_KEY=%q\n' "$public_key" +} + +# Helper function to generate a rsa 4096 key pair +gen_rsa_4096() { + log_debug "Generating RSA 4096 key pair" + local private_key public_key + + # Generate private key + private_key=$(openssl genrsa 4096) + + # Extract public key + public_key=$(echo "$private_key" | openssl rsa -pubout) + + printf 'export RSA_4096_PUBLIC_KEY=%q\n' "$public_key" +} + +# Helper function to generate an EC 256 key pair +gen_ec256() { + log_debug "Generating EC 256 key pair" + local private_key public_key + + # Generate private key + private_key=$(openssl ecparam -name prime256v1 -genkey) + + # Extract public key + public_key=$(echo "$private_key" | openssl ec -pubout) + + printf 'export EC_256_PUBLIC_KEY=%q\n' "$public_key" +} + +# Helper function to generate an EC 384 key pair +gen_ec384() { + log_debug "Generating EC 384 key pair" + local private_key public_key + + # Generate private key + private_key=$(openssl ecparam -name secp384r1 -genkey) + + # Extract public key + public_key=$(echo "$private_key" | openssl ec -pubout) + + printf 'export EC_384_PUBLIC_KEY=%q\n' "$public_key" +} + +# Helper function to generate an EC 521 key pair +gen_ec521() { + log_debug "Generating EC 521 key pair" + local private_key public_key + + # Generate private key + private_key=$(openssl ecparam -name secp521r1 -genkey) + + # Extract public key + public_key=$(echo "$private_key" | openssl ec -pubout) + + printf 'export EC_521_PUBLIC_KEY=%q\n' "$public_key" +} + + + diff --git a/e2e/kas-grants.bats b/e2e/kas-grants.bats index d8646f72..91a47c39 100755 --- a/e2e/kas-grants.bats +++ b/e2e/kas-grants.bats @@ -5,7 +5,7 @@ setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" export KAS_URI="https://e2etestkas.com" export KAS_ID=$(./otdfctl $HOST $WITH_CREDS policy kas-registry create --uri "$KAS_URI" --public-key-remote 'https://e2etestkas.com/pub_key' --json | jq -r '.id') diff --git a/e2e/kas-registry.bats b/e2e/kas-registry.bats index 75d2f6a5..461cc6f9 100755 --- a/e2e/kas-registry.bats +++ b/e2e/kas-registry.bats @@ -1,4 +1,5 @@ #!/usr/bin/env bats +load "./helpers.bash" # Tests for kas registry @@ -6,28 +7,27 @@ setup_file() { export CREDSFILE=creds.json echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > $CREDSFILE export WITH_CREDS="--with-client-creds-file $CREDSFILE" - export HOST='--host http://localhost:8080' + export HOST="${HOST:=--host=http://localhost:8080}" export DEBUG_LEVEL="--log-level debug" export REMOTE_KEY='https://hello.world/pubkey' PEM='-----BEGIN CERTIFICATE-----\nMIIC/TCCAeWgAwIBAgIUMu8o8Wh2HTA6TAeLCjC2f\n9pIeIwDQYJKoZIhvcNAQEL\nBQAwDjEMMAoGA1UEAwwDa2FzMB4XDTI0MDYxODE4M\nYyN1oXDTI1MDYxODE4MzYy\nN1owDjEMMAoGA1UEAwwDa2FzMIIBIjANBgkqhkiG9\n0BAQEFAAOCAQ8AMIIBCgKC\nAQEAr1pQjo7piOvPCTtdIENfG8yVi+WV1FUN/6xTD\nrLxZTtAkZ143uHTfP9a1uq\nhW1IoayJOUjnYsnQHzuEBdkZ4Huwzdy6wRneOTRcj\nN+DwnZKmDq1uafzlGsto/B\nhftmilUF4YnnFcDN+vqj2ep3abUkjhkmIQT8pr25b\nxLaiwwOnlyM5VQc8nahgln\n0M0gNWKIWFEJwhj0Zojh1L4djmzqUiOmNHBP4QzSp\n+0+tWoxIoP2OajkJy0IcZH\nq/N9iSzVbg1K/kKg+du/PmdjP+j56lkJOSRzezh+d\n7+GhrBT3UsmPncV3cWVMi8\nEsYCKcT5EMHhaNaG0XDjJmG28wIDAQABo1MwUTAdB\nNVHQ4EFgQUgPTNFczd9j0E\nX37p6HhwPRicBj8wHwYDVR0jBBgwFoAUgPTNFczd9\n0EX37p6HhwPRicBj8wDwYD\nVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCA\nEACKeqFK0JW2a5sKbOBywZ\nik0y2jrDrZPnf0odN5Hm8meenBxmyoByVVFonPeCh\nnYFStDm2QIQ6gYPmtAaCuJ\ntUyNs6LOBmpGbJhTg5yceqWZxXcsfVFwdqqUt66tW\ncOxVTBgk7xzDQOnLgFLjd6\nJVHxMzFLWTQ0kM2UrN8gtOdLk4aeBaK7bTwZPFtFt\naFebQTm4KcfR5zsfLS+8iF\nu1fF9ZJZH6g6blCTxNtwvvyS1U3/KP0VT9YPw95fp\nV2SKOd3z3Y0dJ9A9Ld9MI3\nL/Y/+5m94FB17SIkDEzY3gvNLCIVq88vXyg+ghTHs\nscc3VqE0+Lzrfdzimo31Ed\nNA==\n-----END CERTIFICATE-----' export KID='my_key_123' export CACHED_KEY=$(printf '{"cached":{"keys":[{"kid":"%s","alg":1,"pem":"%s"}]}}' "$KID" "$PEM" ) + + export FIXTURE_PUBLIC_KEY_ID="f478f1cd-df6e-4a55-9603-d961b36ea392" } setup() { - load "${BATS_LIB_PATH}/bats-support/load.bash" - load "${BATS_LIB_PATH}/bats-assert/load.bash" - - # invoke binary with credentials - run_otdfctl_kasr () { - run sh -c "./otdfctl policy kas-registry $HOST $WITH_CREDS $*" - } + setup_helper } teardown() { + log_debug "Running teardown for: $BATS_TEST_NAME" ID=$(echo "$CREATED" | jq -r '.id') run_otdfctl_kasr delete --id "$ID" --force + + cleanup_helper } @test "create registration of a KAS with remote key" { @@ -205,3 +205,575 @@ teardown() { assert_output --partial "Total" assert_line --regexp "Current Offset.*0" } + +@test "create_public_key_success" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + # # Generate the key pair and set variables + # create_ + # log_debug "RSA_2048_PUBLIC_KEY=\"$RSA_2048_PUBLIC_KEY\"" + # eval "$(gen_rsa_4096)" + # log_debug "RSA_4096_PUBLIC_KEY=\"$RSA_4096_PUBLIC_KEY\"" + # eval "$(gen_ec256)" + # log_debug "EC_256_PUBLIC_KEY=\"$EC_256_PUBLIC_KEY\"" + # eval "$(gen_ec384)" + # log_debug "EC_384_PUBLIC_KEY=\"$EC_384_PUBLIC_KEY\"" + # eval "$(gen_ec521)" + # log_debug "EC_521_PUBLIC_KEY=\"$EC_521_PUBLIC_KEY\"" + + KID="test_key_123" + + ########## Creating RSA 2048 public keys ########## + create_public_key "$KAS_ID" "$KID" "$RSA_2048_ALG" + # log_debug "Running ${run_otdfctl_kasr} public-key create --kas $KAS_ID --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$RSA_2048_ALG" --json" + # run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$RSA_2048_ALG" --json + + # log_debug "Raw Output:" # Debug log: Raw output + # log_debug "$output" + + assert_success # Check if the command ran successfully + + # Get the ID of the public key + # PUBLIC_KEY_ID=$(echo "$output" | jq -r '.id') + # PUBLIC_KEY_IDS+=("$PUBLIC_KEY_ID") + + ########## Creating RSA 4096 public keys ########## + create_public_key "$KAS_ID" "$KID" "$RSA_4096_ALG" + # log_debug "Running ${run_otdfctl_kasr} public-key create --kas $KAS_ID --key \"$(echo "$RSA_4096_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$RSA_4096_ALG" --json" + # run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_4096_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$RSA_4096_ALG" --json + + # log_debug "Raw Output:" # Debug log: Raw output + # log_debug "$output" + + assert_success # Check if the command ran successfully + + # Get the ID of the public key + # PUBLIC_KEY_ID=$(echo "$output" | jq -r '.id') + # PUBLIC_KEY_IDS+=("$PUBLIC_KEY_ID") + + ########## Creating EC 256 public keys ########## + create_public_key "$KAS_ID" "$KID" "$EC_256_ALG" + # log_debug "Running ${run_otdfctl_kasr} public-key create --kas $KAS_ID --key \"$(echo "$EC_256_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$EC_256_ALG" --json" + # run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$EC_256_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$EC_256_ALG" --json + + # log_debug "Raw Output:" # Debug log: Raw output + # log_debug "$output" + + assert_success # Check if the command ran successfully + + # Get the ID of the public key + # PUBLIC_KEY_ID=$(echo "$output" | jq -r '.id') + # PUBLIC_KEY_IDS+=("$PUBLIC_KEY_ID") + + ########## Creating EC 384 public keys ########## + create_public_key "$KAS_ID" "$KID" "$EC_384_ALG" + # log_debug "Running ${run_otdfctl_kasr} public-key create --kas $KAS_ID --key \"$(echo "$EC_384_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$EC_384_ALG" --json" + # run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$EC_384_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$EC_384_ALG" --json + + # log_debug "Raw Output:" # Debug log: Raw output + # log_debug "$output" + + assert_success # Check if the command ran successfully + + # Get the ID of the public key + # PUBLIC_KEY_ID=$(echo "$output" | jq -r '.id') + # PUBLIC_KEY_IDS+=("$PUBLIC_KEY_ID") +} + +@test "create_public_key_required_flags" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + # Generate the key pair and set variables + eval "$(gen_rsa_2048)" + log_debug "RSA_2048_PUBLIC_KEY=\"$RSA_2048_PUBLIC_KEY\"" + + ALG="rsa:2048" + KID="test_key_123" + + # Missing KAS Flag + run_otdfctl_kasr public-key create --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$ALG" --json + assert_failure # Check if the command failed requiring the KAS flag + assert_output --partial "Flag '--kas' is required" + + # Missing Key Flag + run_otdfctl_kasr public-key create --kas "$KAS_ID" --key-id "$KID" --algorithm "$ALG" --json + assert_failure # Check if the command failed requiring the Key flag + assert_output --partial "Flag '--key' is required" + + # Missing Key ID Flag + run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --algorithm "$ALG" --json + assert_failure # Check if the command failed requiring the Key ID flag + assert_output --partial "Flag '--key-id' is required" + + # Missing Algorithm Flag + run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --json + assert_failure # Check if the command failed requiring the Algorithm flag + assert_output --partial "Flag '--algorithm' is required" +} + +@test "create_public_key_invalid_algorithm" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + # Generate the key pair and set + eval "$(gen_rsa_2048)" + log_debug "RSA_2048_PUBLIC_KEY=\"$RSA_2048_PUBLIC_KEY\"" + + ALG="rsa:2048" + KID="test_key_123" + + # Invalid Algorithm + run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "rsa:4096" --json + assert_failure # Check if the command failed with an invalid algorithm + assert_output --partial "invalid rsa key size" + + # Invalid Algorithm + run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "ec:secp256r1" --json + assert_failure # Check if the command failed with an invalid algorithm + assert_output --partial "ey algorithm does not match the provided algorithm" + + # Unsupported Algorithm + run_otdfctl_kasr public-key create --kas "$KAS_ID" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "rsa:1024" --json + assert_failure # Check if the command failed with an unsupported algorithm + assert_output --partial "unsupported algorithm" +} + +@test "add_public_key_by_kas_uri" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + # Generate the key pair and set variables + # eval "$(gen_rsa_2048)" + # log_debug "PUBLIC_KEY=\"$RSA_2048_PUBLIC_KEY\"" + + ALG="rsa:2048" + KID="test_key_123" + + create_public_key "$KAS_URI" "$KID" "$ALG" + # log_debug "Running ${run_otdfctl_kasr} public-key create --kas $KAS_ID --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$RSA_2048_ALG" --json" + # run_otdfctl_kasr public-key create --kas "$KAS_URI" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$RSA_2048_ALG" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + +} + +@test "add_public_key_by_kas_name" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + # Generate the key pair and set variables + # eval "$(gen_rsa_2048)" + # log_debug "PUBLIC_KEY=\"$RSA_2048_PUBLIC_KEY\"" + + ALG="rsa:2048" + KID="test_key_123" + + create_public_key "$KAS_NAME" "$KID" "$ALG" + # log_debug "Running ${run_otdfctl_kasr} public-key create --kas "$KAS_NAME" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "rsa:2048" --json" + # run_otdfctl_kasr public-key create --kas "$KAS_NAME" --key \"$(echo "$RSA_2048_PUBLIC_KEY" | base64)\" --key-id "$KID" --algorithm "$ALG" --json + + # log_debug "Raw Output:" # Debug log: Raw output + # log_debug "$output" + + assert_success # Check if the command ran successfully +} + +@test "update_public_key_labels" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Update the public key with labels + log_debug "Running ${run_otdfctl_kasr} public-key update --id $PUBLIC_KEY_ID --label test=test --json" + run_otdfctl_kasr public-key update --id "$PUBLIC_KEY_ID" --label test=test --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Get public key by ID and check if the labels are set + log_debug "Running ${run_otdfctl_kasr} public-key get --id $PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key get --id "$PUBLIC_KEY_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Check json response for the labels + echo "$output" | jq -e '.metadata.labels | has("test")' || fail "Label not found" +} + +@test "update_public_key_labels_force" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" "--label test=test" + + # Update the public key with labels + log_debug "Running ${run_otdfctl_kasr} public-key update --id $PUBLIC_KEY_ID --label test1=test1 --force-replace-labels --json" + run_otdfctl_kasr public-key update --id "$PUBLIC_KEY_ID" --label test1=test1 --force-replace-labels --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Get public key by ID and check if the labels are set + log_debug "Running ${run_otdfctl_kasr} public-key get --id $PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key get --id "$PUBLIC_KEY_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Check json response for the labels + echo "$output" | jq -e '.metadata.labels | (has("test") | not) and has("test1")' || fail "Labels check failed" +} + +@test "get_public_key" { + log_info "Starting test: $BATS_TEST_NAME" + + log_debug "Running ${run_otdfctl_kasr} public-key get --id $FIXTURE_PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key get --id $FIXTURE_PUBLIC_KEY_ID --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + if ! echo "$output" | jq -e ; then + fail "Output is not valid JSON" + fi + + # Parse the JSON output using jq + output_json=$(echo "$output" | jq -c '.') + + echo "$output_json" | jq -e ' + .[0] as $root | + [ + if $root.id then empty else "id" end, + if $root.is_active then empty else "is_active" end, + if $root.was_mapped then empty else "was_mapped" end, + if $root.public_key then empty else "public_key" end, + if $root.public_key then ( + if $root.public_key.pem then empty else "public_key.pem" end, + if $root.public_key.kid then empty else "public_key.kid" end, + if $root.public_key.alg then empty else "public_key.alg" end + ) else empty end, + if $root.kas then empty else "kas" end, + if $root.kas then ( + if $root.kas.id then empty else "kas.id" end, + if $root.kas.uri then empty else "kas.uri" end, + if $root.kas.name then empty else "kas.name" end + ) else empty end, + if $root.metadata then empty else "metadata" end + ] | if length > 0 then error("Missing fields: " + join(", ")) else true end +' || fail "Structure validation failed" +} + +@test "get_public_key_required_flags" { + log_info "Starting test: $BATS_TEST_NAME" + # Missing ID Flag + run_otdfctl_kasr public-key get --json + assert_failure # Check if the command failed requiring the ID flag + assert_output --partial "Flag '--id' is required" +} + + +@test "list_public_keys" { + log_info "Starting test: $BATS_TEST_NAME" + + log_debug "Running ${run_otdfctl_kasr} public-key list --json" + run_otdfctl_kasr public-key list --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Parse the JSON output using jq + output_json=$(echo "$output" | jq -c '.') + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output_json" | jq 'length')" -gt 0 ] + + echo "$output_json" | jq -e ' + .[0] as $root | + [ + if $root.id then empty else "id" end, + if $root.is_active then empty else "is_active" end, + if $root.was_mapped then empty else "was_mapped" end, + if $root.public_key then empty else "public_key" end, + if $root.public_key then ( + if $root.public_key.pem then empty else "public_key.pem" end, + if $root.public_key.kid then empty else "public_key.kid" end, + if $root.public_key.alg then empty else "public_key.alg" end + ) else empty end, + if $root.kas then empty else "kas" end, + if $root.kas then ( + if $root.kas.id then empty else "kas.id" end, + if $root.kas.uri then empty else "kas.uri" end, + if $root.kas.name then empty else "kas.name" end + ) else empty end, + if $root.metadata then empty else "metadata" end + ] | if length > 0 then error("Missing fields: " + join(", ")) else true end +' || fail "Structure validation failed" +} + +@test "list_public_keys_by_kas" { + # Create a KAS to Filter By + ALG="rsa:2048" + + create_kas "$KAS_URI" "$KAS_NAME" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Filter By ID + log_debug "Running ${run_otdfctl_kasr} public-key list --kas "$KAS_ID" --json" + run_otdfctl_kasr public-key list --kas "$KAS_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -r '.[].kas.id' | while read -r id; do + log_debug "Checking KAS ID: $id against $KAS_ID" + [ "$id" = "$KAS_ID" ] || fail "KAS ID does not match" + done + + + # Filter By URI + log_debug "Running ${run_otdfctl_kasr} public-key list --kas "$KAS_URI" --json" + run_otdfctl_kasr public-key list --kas "$KAS_URI" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -r '.[].kas.uri' | while read -r uri; do + log_debug "Checking KAS ID: $uri against $KAS_URI" + [ "$uri" = "$KAS_URI" ] || fail "KAS ID does not match" + done + + + # Filter By Name + log_debug "Running ${run_otdfctl_kasr} public-key list --kas "$KAS_NAME" --json" + run_otdfctl_kasr public-key list --kas "$KAS_NAME" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -r '.[].kas.name' | while read -r name; do + log_debug "Checking KAS ID: $name against $KAS_NAME" + [ "$name" = "$KAS_NAME" ] || fail "KAS ID does not match" + done +} + +@test "list_public_key_mappings" { + log_info "Starting test: $BATS_TEST_NAME" + + log_debug "Running ${run_otdfctl_kasr} public-key list-mappings --json" + run_otdfctl_kasr public-key list-mappings --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -e ' + .[0].public_keys[] | ( + (.key | has("id")) and + (.key | has("is_active")) and + (.key | has("was_mapped")) and + (.key | has("public_key")) and + (.key.public_key | has("pem") and has("kid") and has("alg")) + ) or error("Missing required public key fields in response structure") + ' || fail "Structure validation failed" +} + +@test "list_public_key_mappings_by_kas" { + log_info "Starting test: $BATS_TEST_NAME" + + # Create a KAS to Filter By + ALG="rsa:2048" + + create_kas "$KAS_URI" "$KAS_NAME" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Filter By ID + log_debug "Running ${run_otdfctl_kasr} public-key list-mappings --kas "$KAS_ID" --json" + run_otdfctl_kasr public-key list-mappings --kas "$KAS_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -r '.[].kas_id' | while read -r id; do + log_debug "Checking KAS ID: $id against $KAS_ID" + [ "$id" = "$KAS_ID" ] || fail "KAS ID does not match" + done + + # Filter By URI + log_debug "Running ${run_otdfctl_kasr} public-key list-mappings --kas "$KAS_URI" --json" + run_otdfctl_kasr public-key list-mappings --kas "$KAS_URI" --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -r '.[].kas_uri' | while read -r uri; do + log_debug "Checking KAS ID: $uri against $KAS_URI" + [ "$uri" = "$KAS_URI" ] || fail "KAS ID does not match" + done + + + # Filter By Name + log_debug "Running ${run_otdfctl_kasr} public-key list-mappings --kas "$KAS_NAME" --json" + run_otdfctl_kasr public-key list-mappings --kas "$KAS_NAME" --json + + log_debug "Raw Output:" # Debug log: Raw output + + assert_success # Check if the command ran successfully + + # Check if the output is valid JSON and is an array (without using jq -t) + if ! echo "$output" | jq -e '.[0]'; then + fail "Output is not a JSON array" + fi + + # Check if the output is not empty (contains at least one key) + [ "$(echo "$output" | jq 'length')" -gt 0 ] + + echo "$output" | jq -r '.[].kas_name' | while read -r name; do + log_debug "Checking KAS ID: $name against $KAS_NAME" + [ "$name" = "$KAS_NAME" ] || fail "KAS ID does not match" + done +} + +@test "activate_deactivate_public_key" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Deactivate the public key + log_debug "Running ${run_otdfctl_kasr} public-key deactivate --id $PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key deactivate --id "$PUBLIC_KEY_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Get public key by ID and check if the key is deactivated + log_debug "Running ${run_otdfctl_kasr} public-key get --id $PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key get --id "$PUBLIC_KEY_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check json response for the is_active flag + echo "$output" | jq -e '.is_active == {}' || fail "Public key is still active" + + # Activate the public key + log_debug "Running ${run_otdfctl_kasr} public-key activate --id $PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key activate --id "$PUBLIC_KEY_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Get public key by ID and check if the key is activated + log_debug "Running ${run_otdfctl_kasr} public-key get --id $PUBLIC_KEY_ID --json" + run_otdfctl_kasr public-key get --id "$PUBLIC_KEY_ID" --json + + log_debug "Raw Output:" # Debug log: Raw output + log_debug "$output" + + assert_success # Check if the command ran successfully + + # Check json response for the is_active flag + echo "$output" | jq -e '.is_active.value' || fail "Public key is not active" +} \ No newline at end of file diff --git a/e2e/namespaces.bats b/e2e/namespaces.bats index dcb0ee99..cac9f564 100755 --- a/e2e/namespaces.bats +++ b/e2e/namespaces.bats @@ -1,11 +1,12 @@ #!/usr/bin/env bats +load "./helpers.bash" # Tests for namespaces setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" # Create the namespace to be used by other tests @@ -16,8 +17,7 @@ setup_file() { } setup() { - load "${BATS_LIB_PATH}/bats-support/load.bash" - load "${BATS_LIB_PATH}/bats-assert/load.bash" + setup_helper # invoke binary with credentials run_otdfctl_ns () { @@ -25,6 +25,13 @@ setup() { } } +teardown() { + cleanup_helper + + # cleanup + run_otdfctl_ns unsafe delete --id $NS_ID --force +} + teardown_file() { # clear out all test env vars unset HOST WITH_CREDS NS_NAME NS_FQN NS_ID NS_ID_FLAG @@ -190,3 +197,68 @@ teardown_file() { run_otdfctl_ns list --state active echo $output | refute_output --partial "$NS_ID" } + +@test "add_remove_key_to_namespace" { + log_info "Starting test: $BATS_TEST_NAME" + + run_otdfctl_ns create --name keys.test --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + NAMESPACE_ID=$(echo "$output" | jq -r '.id') + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Add the key to the attribute namespace + log_info "Running ${run_otdfctl_ns} keys add --value $NAMESPACE_ID --public-key-id $KID" + run_otdfctl_ns keys add --namespace "$NAMESPACE_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was added to the attribute namespace + log_info "Running ${run_otdfctl_ns} get --id $NAMESPACE_ID" + run_otdfctl_ns get --id "$NAMESPACE_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -r '.keys[].id' | while read -r id; do + log_debug "Checking PK ID: $id against $PUBLIC_KEY_ID" + [ "$id" = "$PUBLIC_KEY_ID" ] || fail "KAS ID does not match" + done + + # Remove the key from the attribute namespaces + log_info "Running ${run_otdfctl_ns} keys remove --namespace $NAMESPACE_ID --public-key-id $PUBLIC_KEY_ID" + run_otdfctl_ns keys remove --namespace "$NAMESPACE_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was removed from the attribute namespaces + log_info "Running ${run_otdfctl_ns} get --id $NAMESPACE_ID" + run_otdfctl_ns get --id "$NAMESPACE_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -e 'has("keys") | not' || fail "KAS ID still present" + + run_otdfctl_ns unsafe delete --id $NAMESPACE_ID --force +} \ No newline at end of file diff --git a/e2e/resource-mapping.bats b/e2e/resource-mapping.bats index b637602b..8fd70ec5 100755 --- a/e2e/resource-mapping.bats +++ b/e2e/resource-mapping.bats @@ -5,7 +5,7 @@ setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" # Create two namespaced values to be used in other tests NS_NAME="resource-mappings.io" diff --git a/e2e/subject-condition-sets.bats b/e2e/subject-condition-sets.bats index 283d2f5e..cad6ef38 100755 --- a/e2e/subject-condition-sets.bats +++ b/e2e/subject-condition-sets.bats @@ -5,7 +5,7 @@ setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" export SCS_1='[{"condition_groups":[{"conditions":[{"operator":1,"subject_external_values":["marketing"],"subject_external_selector_value":".org.name"},{"operator":1,"subject_external_values":["ShinyThing"],"subject_external_selector_value":".team.name"}],"boolean_operator":1}]}]' export SCS_2='[{"condition_groups":[{"conditions":[{"operator":3,"subject_external_values":["piedpiper.com","hooli.com"],"subject_external_selector_value":".emailAddress"},{"operator":1,"subject_external_values":["sales"],"subject_external_selector_value":".department"}],"boolean_operator":2}]}]' diff --git a/e2e/subject-mapping.bats b/e2e/subject-mapping.bats index cfb7b437..bd1a41b9 100755 --- a/e2e/subject-mapping.bats +++ b/e2e/subject-mapping.bats @@ -5,7 +5,7 @@ setup_file() { echo -n '{"clientId":"opentdf","clientSecret":"secret"}' > creds.json export WITH_CREDS='--with-client-creds-file ./creds.json' - export HOST='--host http://localhost:8080' + export HOST="${HOST:---host http://localhost:8080}" # Create two namespaced values to be used in other tests NS_NAME="subject-mappings.net" diff --git a/go.mod b/go.mod index 6f0c1b7d..9094a8d6 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/opentdf/otdfctl go 1.22.7 +replace github.com/opentdf/platform/protocol/go => ../platform/protocol/go + require ( github.com/adrg/frontmatter v0.2.0 github.com/charmbracelet/bubbles v0.18.0 @@ -16,7 +18,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 github.com/opentdf/platform/lib/flattening v0.1.3 - github.com/opentdf/platform/protocol/go v0.2.22 + github.com/opentdf/platform/protocol/go v0.2.24 github.com/opentdf/platform/sdk v0.3.25 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 diff --git a/go.sum b/go.sum index 37e8a9f5..552853b6 100644 --- a/go.sum +++ b/go.sum @@ -228,8 +228,6 @@ github.com/opentdf/platform/lib/flattening v0.1.3 h1:IuOm/wJVXNrzOV676Ticgr0wyBk github.com/opentdf/platform/lib/flattening v0.1.3/go.mod h1:Gs/T+6FGZKk9OAdz2Jf1R8CTGeNRYrq1lZGDeYT3hrY= github.com/opentdf/platform/lib/ocrypto v0.1.7 h1:IcCYRrwmMqntqUE8frmUDg5EZ0WMdldpGeGhbv9+/A8= github.com/opentdf/platform/lib/ocrypto v0.1.7/go.mod h1:4bhKPbRFzURMerH5Vr/LlszHvcoXQbfJXa0bpY7/7yg= -github.com/opentdf/platform/protocol/go v0.2.22 h1:C/jjtwu5yTon8g0ewuN29QE7VXSQHyb2dx9W0U6Oqok= -github.com/opentdf/platform/protocol/go v0.2.22/go.mod h1:skpOCVuWSjUHazLKOkh3nSB057OB4sHICe7MpmJY9KU= github.com/opentdf/platform/sdk v0.3.25 h1:dZEVeWKfbjrnEXKzSado8ebpzIrk2n6R7RSZRbX+FwE= github.com/opentdf/platform/sdk v0.3.25/go.mod h1:F+RGbT2o9GlzWH9s8VkZyUNUEEAWA3V2RSs8jNQHbqM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= diff --git a/pkg/cli/confirm.go b/pkg/cli/confirm.go index 23275110..1454e315 100644 --- a/pkg/cli/confirm.go +++ b/pkg/cli/confirm.go @@ -20,6 +20,7 @@ const ( // text input names InputNameFQN = "fully qualified name (FQN)" InputNameFQNUpdated = "deprecated fully qualified name (FQN) being altered" + InputNameKeyID = "key ID" ) func ConfirmAction(action, resource, id string, force bool) { diff --git a/pkg/cli/style.go b/pkg/cli/style.go index 91d8c23c..ed3335c8 100644 --- a/pkg/cli/style.go +++ b/pkg/cli/style.go @@ -8,32 +8,35 @@ type Color struct { Background lipgloss.CompleteAdaptiveColor } -var colorRed = Color{ - Foreground: lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{ - TrueColor: "#FF0000", - ANSI256: "9", - ANSI: "1", - }, - Dark: lipgloss.CompleteColor{ - TrueColor: "#FF0000", - ANSI256: "9", - ANSI: "1", - }, - }, - Background: lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{ - TrueColor: "#FFD2D2", - ANSI256: "224", - ANSI: "7", - }, - Dark: lipgloss.CompleteColor{ - TrueColor: "#da6b81", - ANSI256: "52", - ANSI: "4", - }, - }, -} +var ( + ColorRed = colorRed + colorRed = Color{ + Foreground: lipgloss.CompleteAdaptiveColor{ + Light: lipgloss.CompleteColor{ + TrueColor: "#FF0000", + ANSI256: "9", + ANSI: "1", + }, + Dark: lipgloss.CompleteColor{ + TrueColor: "#FF0000", + ANSI256: "9", + ANSI: "1", + }, + }, + Background: lipgloss.CompleteAdaptiveColor{ + Light: lipgloss.CompleteColor{ + TrueColor: "#FFD2D2", + ANSI256: "224", + ANSI: "7", + }, + Dark: lipgloss.CompleteColor{ + TrueColor: "#da6b81", + ANSI256: "52", + ANSI: "4", + }, + }, + } +) var colorOrange = Color{ Foreground: lipgloss.CompleteAdaptiveColor{ @@ -90,32 +93,35 @@ var colorYellow = Color{ }, } -var colorGreen = Color{ - Foreground: lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{ - TrueColor: "#008000", - ANSI256: "28", - ANSI: "2", - }, - Dark: lipgloss.CompleteColor{ - TrueColor: "#008000", - ANSI256: "28", - ANSI: "2", - }, - }, - Background: lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{ - TrueColor: "#D2FFD2", - ANSI256: "157", - ANSI: "7", - }, - Dark: lipgloss.CompleteColor{ - TrueColor: "#29cf68", - ANSI256: "22", - ANSI: "4", - }, - }, -} +var ( + ColorGreen = colorGreen + colorGreen = Color{ + Foreground: lipgloss.CompleteAdaptiveColor{ + Light: lipgloss.CompleteColor{ + TrueColor: "#008000", + ANSI256: "28", + ANSI: "2", + }, + Dark: lipgloss.CompleteColor{ + TrueColor: "#008000", + ANSI256: "28", + ANSI: "2", + }, + }, + Background: lipgloss.CompleteAdaptiveColor{ + Light: lipgloss.CompleteColor{ + TrueColor: "#D2FFD2", + ANSI256: "157", + ANSI: "7", + }, + Dark: lipgloss.CompleteColor{ + TrueColor: "#29cf68", + ANSI256: "22", + ANSI: "4", + }, + }, + } +) var colorBlue = Color{ Foreground: lipgloss.CompleteAdaptiveColor{ diff --git a/pkg/cli/table.go b/pkg/cli/table.go index 7564bf9a..864783ef 100644 --- a/pkg/cli/table.go +++ b/pkg/cli/table.go @@ -22,7 +22,8 @@ func NewTable(cols ...table.Column) table.Model { BorderRounded(). WithBaseStyle(styleTable). WithNoPagination(). - WithTargetWidth(TermWidth()) + WithTargetWidth(TermWidth()). + WithMultiline(true) } func NewUUIDColumn() table.Column { diff --git a/pkg/handlers/attribute.go b/pkg/handlers/attribute.go index 3596bef1..d60980b6 100644 --- a/pkg/handlers/attribute.go +++ b/pkg/handlers/attribute.go @@ -1,12 +1,16 @@ package handlers import ( + "context" + "errors" "fmt" + "github.com/opentdf/otdfctl/pkg/utils" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/attributes" "github.com/opentdf/platform/protocol/go/policy/unsafe" + "google.golang.org/grpc/status" ) // TODO: Might be useful to map out the attribute rule definitions for help text in the CLI and TUI @@ -31,10 +35,20 @@ func (e *CreateAttributeError) Error() string { return "Error creating attribute" } -func (h Handler) GetAttribute(id string) (*policy.Attribute, error) { - resp, err := h.sdk.Attributes.GetAttribute(h.ctx, &attributes.GetAttributeRequest{ - Id: id, - }) +func (h Handler) GetAttribute(identifier string) (*policy.Attribute, error) { + req := &attributes.GetAttributeRequest{} + + if utils.IsUUID(identifier) { + req.Identifier = &attributes.GetAttributeRequest_AttributeId{ + AttributeId: identifier, + } + } else { + req.Identifier = &attributes.GetAttributeRequest_Fqn{ + Fqn: identifier, + } + } + + resp, err := h.sdk.Attributes.GetAttribute(h.ctx, req) if err != nil { return nil, err } @@ -188,3 +202,58 @@ func GetAttributeRuleFromReadableString(rule string) (policy.AttributeRuleTypeEn } return 0, fmt.Errorf("invalid attribute rule: %s, must be one of [%s, %s, %s]", rule, AttributeRuleAllOf, AttributeRuleAnyOf, AttributeRuleHierarchy) } + +func (h Handler) AddPublicKeyToDefinition(ctx context.Context, definition, publicKeyID string) (*attributes.AttributeKey, error) { + ak := &attributes.AttributeKey{ + KeyId: publicKeyID, + } + + if utils.IsUUID(definition) { + ak.AttributeId = definition + } else { + def, err := h.GetAttribute(definition) + if err != nil { + return nil, err + } + ak.AttributeId = def.GetId() + } + + resp, err := h.sdk.Attributes.AssignKeyToAttribute(ctx, &attributes.AssignKeyToAttributeRequest{ + AttributeKey: ak, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + + return resp.GetAttributeKey(), nil +} + +func (h Handler) RemovePublicKeyFromDefinition(ctx context.Context, definition, publicKeyID string) (*attributes.AttributeKey, error) { + ak := &attributes.AttributeKey{ + KeyId: publicKeyID, + } + + if utils.IsUUID(definition) { + ak.AttributeId = definition + } else { + def, err := h.GetAttribute(definition) + if err != nil { + return nil, err + } + ak.AttributeId = def.GetId() + } + + _, err := h.sdk.Attributes.RemoveKeyFromAttribute(ctx, &attributes.RemoveKeyFromAttributeRequest{ + AttributeKey: ak, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + return ak, nil +} + +func (h Handler) ListPublicKeyDefinitionMappings(ctx context.Context, definitionID string, offset, limit int32) error { + return nil +} diff --git a/pkg/handlers/attributeValues.go b/pkg/handlers/attributeValues.go index a62ff7d6..309704ef 100644 --- a/pkg/handlers/attributeValues.go +++ b/pkg/handlers/attributeValues.go @@ -1,10 +1,15 @@ package handlers import ( + "context" + "errors" + + "github.com/opentdf/otdfctl/pkg/utils" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/attributes" "github.com/opentdf/platform/protocol/go/policy/unsafe" + "google.golang.org/grpc/status" ) func (h *Handler) ListAttributeValues(attributeId string, state common.ActiveStateEnum, limit, offset int32) ([]*policy.Value, *policy.PageResponse, error) { @@ -36,10 +41,20 @@ func (h *Handler) CreateAttributeValue(attributeId string, value string, metadat return h.GetAttributeValue(resp.GetValue().GetId()) } -func (h *Handler) GetAttributeValue(id string) (*policy.Value, error) { - resp, err := h.sdk.Attributes.GetAttributeValue(h.ctx, &attributes.GetAttributeValueRequest{ - Id: id, - }) +func (h *Handler) GetAttributeValue(identifier string) (*policy.Value, error) { + req := &attributes.GetAttributeValueRequest{} + + if utils.IsUUID(identifier) { + req.Identifier = &attributes.GetAttributeValueRequest_ValueId{ + ValueId: identifier, + } + } else { + req.Identifier = &attributes.GetAttributeValueRequest_Fqn{ + Fqn: identifier, + } + } + + resp, err := h.sdk.Attributes.GetAttributeValue(h.ctx, req) if err != nil { return nil, err } @@ -102,3 +117,58 @@ func (h Handler) UnsafeUpdateAttributeValue(id, value string) error { _, err := h.sdk.Unsafe.UnsafeUpdateAttributeValue(h.ctx, req) return err } + +func (h Handler) AddPublicKeyToValue(ctx context.Context, value, publicKeyID string) (*attributes.ValueKey, error) { + av := &attributes.ValueKey{ + KeyId: publicKeyID, + } + + if utils.IsUUID(value) { + av.ValueId = value + } else { + def, err := h.GetAttributeValue(value) + if err != nil { + return nil, err + } + av.ValueId = def.GetId() + } + + resp, err := h.sdk.Attributes.AssignKeyToValue(ctx, &attributes.AssignKeyToValueRequest{ + ValueKey: av, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + + return resp.GetValueKey(), nil +} + +func (h Handler) RemovePublicKeyFromValue(ctx context.Context, value, publicKeyID string) (*attributes.ValueKey, error) { + vk := &attributes.ValueKey{ + KeyId: publicKeyID, + } + + if utils.IsUUID(value) { + vk.ValueId = value + } else { + def, err := h.GetAttributeValue(value) + if err != nil { + return nil, err + } + vk.ValueId = def.GetId() + } + + _, err := h.sdk.Attributes.RemoveKeyFromValue(ctx, &attributes.RemoveKeyFromValueRequest{ + ValueKey: vk, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + return vk, nil +} + +func (h Handler) ListPublicKeyValueMappings(ctx context.Context, attributeID string, offset, limit int32) error { + return nil +} diff --git a/pkg/handlers/kas-public-keys.go b/pkg/handlers/kas-public-keys.go new file mode 100644 index 00000000..9f5b5823 --- /dev/null +++ b/pkg/handlers/kas-public-keys.go @@ -0,0 +1,170 @@ +package handlers + +import ( + "errors" + + "github.com/opentdf/otdfctl/pkg/utils" + "github.com/opentdf/platform/protocol/go/common" + "github.com/opentdf/platform/protocol/go/policy" + "github.com/opentdf/platform/protocol/go/policy/kasregistry" + "github.com/opentdf/platform/protocol/go/policy/unsafe" + "google.golang.org/grpc/status" +) + +func (h Handler) CreatePublicKey(kas, pk, kid, alg string, metadata *common.MetadataMutable) (*policy.Key, error) { + // Check if alg is valid + algEnum, err := algToEnum(alg) + if err != nil { + return nil, err + } + + // Key ID can't be more than 32 characters + if len(kid) > 32 { + return nil, errors.New("key id must be less than 32 characters") + } + + // Get KAS UUID if it's not a UUID + + if !utils.IsUUID(kas) { + k, err := h.GetKasRegistryEntry(kas) + if err != nil { + return nil, err + } + kas = k.GetId() + } + + // Create the public key + resp, err := h.sdk.KeyAccessServerRegistry.CreatePublicKey(h.ctx, &kasregistry.CreatePublicKeyRequest{ + KasId: kas, + Key: &policy.KasPublicKey{ + Kid: kid, + Alg: algEnum, + Pem: pk, + }, + Metadata: metadata, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + + return h.GetPublicKey(resp.GetKey().GetId()) +} + +func (h Handler) UpdatePublicKey(id string, metadata *common.MetadataMutable, behavior common.MetadataUpdateEnum) (*policy.Key, error) { + resp, err := h.sdk.KeyAccessServerRegistry.UpdatePublicKey(h.ctx, &kasregistry.UpdatePublicKeyRequest{ + Id: id, + Metadata: metadata, + MetadataUpdateBehavior: behavior, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + + return resp.GetKey(), nil +} + +func (h Handler) GetPublicKey(id string) (*policy.Key, error) { + resp, err := h.sdk.KeyAccessServerRegistry.GetPublicKey(h.ctx, &kasregistry.GetPublicKeyRequest{ + Identifier: &kasregistry.GetPublicKeyRequest_Id{Id: id}, + }) + if err != nil { + return nil, err + } + return resp.GetKey(), nil +} + +func (h Handler) ListPublicKeys(kas string, offset, limit int32) ([]*policy.Key, *policy.PageResponse, error) { + req := &kasregistry.ListPublicKeysRequest{ + Pagination: &policy.PageRequest{ + Offset: offset, + Limit: limit, + }, + } + + switch { + case utils.IsUUID(kas): + req.KasFilter = &kasregistry.ListPublicKeysRequest_KasId{KasId: kas} + case utils.IsURI(kas): + req.KasFilter = &kasregistry.ListPublicKeysRequest_KasUri{KasUri: kas} + case kas != "": + req.KasFilter = &kasregistry.ListPublicKeysRequest_KasName{KasName: kas} + } + + resp, err := h.sdk.KeyAccessServerRegistry.ListPublicKeys(h.ctx, req) + if err != nil { + return nil, nil, err + } + return resp.GetKeys(), resp.GetPagination(), nil +} + +func (h Handler) ListPublicKeyMappings(kas, pkID string, offset, limit int32) ([]*kasregistry.ListPublicKeyMappingResponse_PublicKeyMapping, *policy.PageResponse, error) { + req := &kasregistry.ListPublicKeyMappingRequest{ + PublicKeyId: pkID, + Pagination: &policy.PageRequest{ + Offset: offset, + Limit: limit, + }, + } + + switch { + case utils.IsUUID(kas): + req.KasFilter = &kasregistry.ListPublicKeyMappingRequest_KasId{KasId: kas} + case utils.IsURI(kas): + req.KasFilter = &kasregistry.ListPublicKeyMappingRequest_KasUri{KasUri: kas} + case kas != "": + req.KasFilter = &kasregistry.ListPublicKeyMappingRequest_KasName{KasName: kas} + } + + resp, err := h.sdk.KeyAccessServerRegistry.ListPublicKeyMapping(h.ctx, req) + if err != nil { + return nil, nil, err + } + + return resp.GetPublicKeyMappings(), resp.GetPagination(), nil +} + +func (h Handler) DeactivatePublicKey(id string) error { + _, err := h.sdk.KeyAccessServerRegistry.DeactivatePublicKey(h.ctx, &kasregistry.DeactivatePublicKeyRequest{Id: id}) + if err != nil { + s := status.Convert(err) + return errors.New(s.Message()) + } + return nil +} + +func (h Handler) ActivatePublicKey(id string) error { + _, err := h.sdk.KeyAccessServerRegistry.ActivatePublicKey(h.ctx, &kasregistry.ActivatePublicKeyRequest{Id: id}) + if err != nil { + s := status.Convert(err) + return errors.New(s.Message()) + } + return nil +} + +func (h Handler) UnsafeDeletePublicKey(id string) error { + _, err := h.sdk.Unsafe.UnsafeDeletePublicKey(h.ctx, &unsafe.UnsafeDeletePublicKeyRequest{Id: id}) + if err != nil { + s := status.Convert(err) + return errors.New(s.Message()) + } + return nil +} + +func algToEnum(alg string) (policy.KasPublicKeyAlgEnum, error) { + switch alg { + case "rsa:2048": + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048, nil + case "rsa:4096": + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096, nil + case "ec:secp256r1": + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1, nil + case "ec:secp384r1": + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1, nil + case "ec:secp521r1": + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1, nil + default: + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED, errors.New("unsupported algorithm. supported algorithms are rsa:2048, rsa:4096, ec:secp256r1, ec:secp384r1, ec:secp521r1") + } +} diff --git a/pkg/handlers/kas-registry.go b/pkg/handlers/kas-registry.go index 7c0fbe0d..492edef6 100644 --- a/pkg/handlers/kas-registry.go +++ b/pkg/handlers/kas-registry.go @@ -1,15 +1,26 @@ package handlers import ( + "github.com/opentdf/otdfctl/pkg/utils" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/kasregistry" ) -func (h Handler) GetKasRegistryEntry(id string) (*policy.KeyAccessServer, error) { - resp, err := h.sdk.KeyAccessServerRegistry.GetKeyAccessServer(h.ctx, &kasregistry.GetKeyAccessServerRequest{ - Id: id, - }) +func (h Handler) GetKasRegistryEntry(identifier string) (*policy.KeyAccessServer, error) { + req := &kasregistry.GetKeyAccessServerRequest{} + + switch { + case utils.IsUUID(identifier): + req.Identifier = &kasregistry.GetKeyAccessServerRequest_KasId{KasId: identifier} + case utils.IsURI(identifier): + req.Identifier = &kasregistry.GetKeyAccessServerRequest_Uri{Uri: identifier} + default: + // At this point we assume its a kas name + req.Identifier = &kasregistry.GetKeyAccessServerRequest_Name{Name: identifier} + } + + resp, err := h.sdk.KeyAccessServerRegistry.GetKeyAccessServer(h.ctx, req) if err != nil { return nil, err } diff --git a/pkg/handlers/namespaces.go b/pkg/handlers/namespaces.go index fe773472..0d1df891 100644 --- a/pkg/handlers/namespaces.go +++ b/pkg/handlers/namespaces.go @@ -1,16 +1,31 @@ package handlers import ( + "context" + "errors" + + "github.com/opentdf/otdfctl/pkg/utils" "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/namespaces" "github.com/opentdf/platform/protocol/go/policy/unsafe" + "google.golang.org/grpc/status" ) -func (h Handler) GetNamespace(id string) (*policy.Namespace, error) { - resp, err := h.sdk.Namespaces.GetNamespace(h.ctx, &namespaces.GetNamespaceRequest{ - Id: id, - }) +func (h Handler) GetNamespace(identifier string) (*policy.Namespace, error) { + nsReq := new(namespaces.GetNamespaceRequest) + + if utils.IsUUID(identifier) { + nsReq.Identifier = &namespaces.GetNamespaceRequest_NamespaceId{ + NamespaceId: identifier, + } + } else { + nsReq.Identifier = &namespaces.GetNamespaceRequest_Fqn{ + Fqn: identifier, + } + } + + resp, err := h.sdk.Namespaces.GetNamespace(h.ctx, nsReq) if err != nil { return nil, err } @@ -104,3 +119,58 @@ func (h Handler) UnsafeUpdateNamespace(id, name string) (*policy.Namespace, erro return h.GetNamespace(id) } + +func (h Handler) AddPublicKeyToNamespace(ctx context.Context, nameSpace, publicKeyID string) (*namespaces.NamespaceKey, error) { + nk := &namespaces.NamespaceKey{ + KeyId: publicKeyID, + } + + if utils.IsUUID(nameSpace) { + nk.NamespaceId = nameSpace + } else { + nss, err := h.GetNamespace(nameSpace) + if err != nil { + return nil, err + } + nk.NamespaceId = nss.GetId() + } + + resp, err := h.sdk.Namespaces.AssignKeyToNamespace(ctx, &namespaces.AssignKeyToNamespaceRequest{ + NamespaceKey: nk, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + + return resp.GetNamespaceKey(), nil +} + +func (h Handler) RemovePublicKeyFromNamespace(ctx context.Context, nameSpace, publicKeyID string) (*namespaces.NamespaceKey, error) { + nk := &namespaces.NamespaceKey{ + KeyId: publicKeyID, + } + + if utils.IsUUID(nameSpace) { + nk.NamespaceId = nameSpace + } else { + nss, err := h.GetNamespace(nameSpace) + if err != nil { + return nil, err + } + nk.NamespaceId = nss.GetId() + } + _, err := h.sdk.Namespaces.RemoveKeyFromNamespace(ctx, &namespaces.RemoveKeyFromNamespaceRequest{ + NamespaceKey: nk, + }) + if err != nil { + s := status.Convert(err) + return nil, errors.New(s.Message()) + } + + return nk, nil +} + +func (h Handler) ListPublicKeyNamespaceMappings(ctx context.Context, namespaceID string, offset, limit int32) error { + return nil +} diff --git a/pkg/utils/validators.go b/pkg/utils/validators.go index 8569aaf9..245fab79 100644 --- a/pkg/utils/validators.go +++ b/pkg/utils/validators.go @@ -4,6 +4,8 @@ import ( "errors" "net/url" "strings" + + "github.com/google/uuid" ) func NormalizeEndpoint(endpoint string) (*url.URL, error) { @@ -31,3 +33,16 @@ func NormalizeEndpoint(endpoint string) (*url.URL, error) { } return u, nil } + +func IsUUID(u string) bool { + _, err := uuid.Parse(u) + return err == nil +} + +func IsURI(u string) bool { + ur, err := url.Parse(u) + if err != nil { + return false + } + return ur.IsAbs() +} From be72dc3576946fc4a03f3fb9d601cb581a271a21 Mon Sep 17 00:00:00 2001 From: Sean Trantalis Date: Mon, 10 Feb 2025 21:42:19 -0500 Subject: [PATCH 2/7] update documentation --- docs/man/policy/attributes/keys/_index.md | 6 ++++- docs/man/policy/attributes/keys/add.md | 8 ++++-- docs/man/policy/attributes/keys/list.md | 16 +++++++++++- docs/man/policy/attributes/keys/remove.md | 8 ++++-- .../attributes/namespaces/keys/_index.md | 6 ++++- .../policy/attributes/namespaces/keys/add.md | 10 ++++++-- .../policy/attributes/namespaces/keys/list.md | 16 +++++++++++- .../attributes/namespaces/keys/remove.md | 10 ++++++-- .../policy/attributes/values/keys/_index.md | 6 ++++- docs/man/policy/attributes/values/keys/add.md | 8 ++++-- .../man/policy/attributes/values/keys/list.md | 16 +++++++++++- .../policy/attributes/values/keys/remove.md | 11 ++++++-- .../policy/kas-registry/public-keys/_index.md | 3 ++- .../kas-registry/public-keys/activate.md | 4 +-- .../policy/kas-registry/public-keys/create.md | 20 +++++++++++++-- .../kas-registry/public-keys/deactivate.md | 4 +-- .../policy/kas-registry/public-keys/get.md | 2 +- .../kas-registry/public-keys/list-mappings.md | 25 +++++++++++++++++-- .../policy/kas-registry/public-keys/list.md | 17 ++++++++++++- .../public-keys/mappings/_index.md | 8 ------ .../kas-registry/public-keys/unsafe/_index.md | 10 +------- .../kas-registry/public-keys/unsafe/delete.md | 12 ++------- .../policy/kas-registry/public-keys/update.md | 13 +++------- 23 files changed, 173 insertions(+), 66 deletions(-) delete mode 100644 docs/man/policy/kas-registry/public-keys/mappings/_index.md diff --git a/docs/man/policy/attributes/keys/_index.md b/docs/man/policy/attributes/keys/_index.md index b7bbb872..44f5ce7d 100644 --- a/docs/man/policy/attributes/keys/_index.md +++ b/docs/man/policy/attributes/keys/_index.md @@ -5,4 +5,8 @@ command: aliases: - k - key ---- \ No newline at end of file +--- + +The `keys` command allows management of public keys associated with an attribute. These keys are then used during the encryption operation to wrap individual key splits when creating a new TDF(Trusted Data Format). + +For more information on how key splitting work, see the [OpenTDF Documentation](https://opentdf.io/components/policy/key_access_grants). \ No newline at end of file diff --git a/docs/man/policy/attributes/keys/add.md b/docs/man/policy/attributes/keys/add.md index 64b9bfbf..2a0da01b 100644 --- a/docs/man/policy/attributes/keys/add.md +++ b/docs/man/policy/attributes/keys/add.md @@ -15,10 +15,14 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Add a public key mapping to an attribute definition. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +otdfctl policy attributes definitions keys add --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --definition=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +otdfctl policy attributes definitions keys add --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --definition=https://example.com/attr/attr1 ``` diff --git a/docs/man/policy/attributes/keys/list.md b/docs/man/policy/attributes/keys/list.md index e944b6f6..ea5d5c8c 100644 --- a/docs/man/policy/attributes/keys/list.md +++ b/docs/man/policy/attributes/keys/list.md @@ -9,4 +9,18 @@ command: shorthand: d description: ID or FQN of the Attribute Definition required: true ---- \ No newline at end of file +--- + +List the public key mappings for an attribute definition. + +## Example + +```shell +# List public key mappings with Definition ID +otdfctl policy attributes definitions keys list --definition=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# List public key mappings with Definition FQN +otdfctl policy attributes definitions keys list --definition=https://example.com/attr/attr1 +``` \ No newline at end of file diff --git a/docs/man/policy/attributes/keys/remove.md b/docs/man/policy/attributes/keys/remove.md index e0ee6a62..8fc35efb 100644 --- a/docs/man/policy/attributes/keys/remove.md +++ b/docs/man/policy/attributes/keys/remove.md @@ -15,10 +15,14 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Remove a public key mapping from an attribute definition. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +otdfctl policy attributes definitions keys remove --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --definition=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +otdfctl policy attributes definitions keys remove --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --definition=https://example.com/attr/attr1 ``` diff --git a/docs/man/policy/attributes/namespaces/keys/_index.md b/docs/man/policy/attributes/namespaces/keys/_index.md index 0a3de089..ca43b7fb 100644 --- a/docs/man/policy/attributes/namespaces/keys/_index.md +++ b/docs/man/policy/attributes/namespaces/keys/_index.md @@ -5,4 +5,8 @@ command: aliases: - k - key ---- \ No newline at end of file +--- + +The `keys` command allows management of public keys associated with an attribute namespace. These keys are then used during the encryption operation to wrap individual key splits when creating a new TDF(Trusted Data Format). + +For more information on how key splitting work, see the [OpenTDF Documentation](https://opentdf.io/components/policy/key_access_grants). diff --git a/docs/man/policy/attributes/namespaces/keys/add.md b/docs/man/policy/attributes/namespaces/keys/add.md index e627d564..84512271 100644 --- a/docs/man/policy/attributes/namespaces/keys/add.md +++ b/docs/man/policy/attributes/namespaces/keys/add.md @@ -15,10 +15,16 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Add a public key mapping to an attribute namespace. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +# Add a public key mapping with Namespace ID +otdfctl policy attributes namespaces keys add --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --namespace=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# Add a public key mapping with Namespace FQN +otdfctl policy attributes namespaces keys add --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --namespace=https://example.namespace ``` diff --git a/docs/man/policy/attributes/namespaces/keys/list.md b/docs/man/policy/attributes/namespaces/keys/list.md index 260a226c..40644d4e 100644 --- a/docs/man/policy/attributes/namespaces/keys/list.md +++ b/docs/man/policy/attributes/namespaces/keys/list.md @@ -9,4 +9,18 @@ command: shorthand: n description: ID or FQN of the Attribute Namespace required: true ---- \ No newline at end of file +--- + +List the public key mappings for an attribute namespace. + +## Example + +```shell +# List public key mappings with Namespace ID +otdfctl policy attributes namespaces keys list --namespace=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# List public key mappings with Namespace FQN +otdfctl policy attributes namespaces keys list --namespace=https://example.namespace +``` \ No newline at end of file diff --git a/docs/man/policy/attributes/namespaces/keys/remove.md b/docs/man/policy/attributes/namespaces/keys/remove.md index b5f6d362..fda1e2d5 100644 --- a/docs/man/policy/attributes/namespaces/keys/remove.md +++ b/docs/man/policy/attributes/namespaces/keys/remove.md @@ -15,10 +15,16 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Remove a public key mapping from an attribute namespace. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +# Remove a public key mapping with Namespace ID +otdfctl policy attributes namespaces keys remove --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --namespace=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# Remove a public key mapping with Namespace FQN +otdfctl policy attributes namespaces keys remove --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --namespace=https://example.namespace ``` diff --git a/docs/man/policy/attributes/values/keys/_index.md b/docs/man/policy/attributes/values/keys/_index.md index 0ec3bbcc..10cbfef0 100644 --- a/docs/man/policy/attributes/values/keys/_index.md +++ b/docs/man/policy/attributes/values/keys/_index.md @@ -5,4 +5,8 @@ command: aliases: - k - key ---- \ No newline at end of file +--- + +The `keys` command allows management of public keys associated with an attribute value. These keys are then used during the encryption operation to wrap individual key splits when creating a new TDF(Trusted Data Format). + +For more information on how key splitting work, see the [OpenTDF Documentation](https://opentdf.io/components/policy/key_access_grants). \ No newline at end of file diff --git a/docs/man/policy/attributes/values/keys/add.md b/docs/man/policy/attributes/values/keys/add.md index 591c1597..76fe2940 100644 --- a/docs/man/policy/attributes/values/keys/add.md +++ b/docs/man/policy/attributes/values/keys/add.md @@ -15,10 +15,14 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Add a public key mapping to an attribute value. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +otdfctl policy attributes values keys add --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --value=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +otdfctl policy attributes values keys add --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --value=https://example.com/attr/attr1/value/val1 ``` diff --git a/docs/man/policy/attributes/values/keys/list.md b/docs/man/policy/attributes/values/keys/list.md index ff4c6837..f57f1164 100644 --- a/docs/man/policy/attributes/values/keys/list.md +++ b/docs/man/policy/attributes/values/keys/list.md @@ -9,4 +9,18 @@ command: shorthand: v description: ID or FQN of the Attribute Value required: true ---- \ No newline at end of file +--- + +List the public key mappings for an attribute value. + +## Example + +```shell +# List public key mappings with Value ID +otdfctl policy attributes values keys list --value=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# List public key mappings with Value FQN +otdfctl policy attributes values keys list --value=https://example.com/attr/attr1/value/val1 +``` \ No newline at end of file diff --git a/docs/man/policy/attributes/values/keys/remove.md b/docs/man/policy/attributes/values/keys/remove.md index cdcc1be0..55eed382 100644 --- a/docs/man/policy/attributes/values/keys/remove.md +++ b/docs/man/policy/attributes/values/keys/remove.md @@ -15,10 +15,17 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Remove a public key mapping from an attribute value. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +# Remove a public key mapping with Value ID +otdfctl policy attributes values keys remove --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --value=62857b55-560c-4b67-96e3-33e4670ecb3b ``` + +```shell +# Remove a public key mapping with Value FQN +otdfctl policy attributes values keys remove --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b --value=https://example.com/attr/attr1/value/val1 +``` + diff --git a/docs/man/policy/kas-registry/public-keys/_index.md b/docs/man/policy/kas-registry/public-keys/_index.md index 5053325d..9bac99a8 100644 --- a/docs/man/policy/kas-registry/public-keys/_index.md +++ b/docs/man/policy/kas-registry/public-keys/_index.md @@ -1,9 +1,10 @@ --- title: Manage Key Access Server Public Keys command: - name: public-key + name: public-keys aliases: - pk + - public-key --- diff --git a/docs/man/policy/kas-registry/public-keys/activate.md b/docs/man/policy/kas-registry/public-keys/activate.md index 0b2e62e1..f43f23f4 100644 --- a/docs/man/policy/kas-registry/public-keys/activate.md +++ b/docs/man/policy/kas-registry/public-keys/activate.md @@ -11,10 +11,10 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Activate a public key. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +otdfctl policy kas-registry public-keys activate --id=62857b55-560c-4b67-96e3-33e4670ecb3b ``` diff --git a/docs/man/policy/kas-registry/public-keys/create.md b/docs/man/policy/kas-registry/public-keys/create.md index 166f35f7..f0a5c82b 100644 --- a/docs/man/policy/kas-registry/public-keys/create.md +++ b/docs/man/policy/kas-registry/public-keys/create.md @@ -26,10 +26,26 @@ command: --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Add a public key to a Key Access Server. The public key must be in PEM format. It can be base64 encoded or plain text. + +If a key exists with the same algorithm already, the new key will be marked as active and the existing key will be marked as inactive. The namespace, attribute and value mappings will be updated to point to the new key. ## Example ```shell -otdfctl policy kas-registry public-key add --kas-id 1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q=\n-----END CERTIFICATE-----\n" --algorithm rsa:2048 +# Add a public key to a Key Access Server By ID +otdfctl policy kas-registry public-key create --kas 62857b55-560c-4b67-96e3-33e4670ecb3b --key-id key-1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q=\n-----END CERTIFICATE-----\n" --algorithm rsa:2048 +``` + +```shell +# Add a public key to a Key Access Server By Name +otdfctl policy kas-registry public-key +create --kas kas-1 --key-id key-1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q=\n-----END CERTIFICATE-----\n" --algorithm rsa:2048 ``` + +```shell +# Add a public key to a Key Access Server By URI +otdfctl policy kas-registry public-key +create --kas https://example.com/kas --key-id key-1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q=\n-----END CERTIFICATE-----\n" --algorithm rsa:2048 +``` + diff --git a/docs/man/policy/kas-registry/public-keys/deactivate.md b/docs/man/policy/kas-registry/public-keys/deactivate.md index 07f611e8..17649b81 100644 --- a/docs/man/policy/kas-registry/public-keys/deactivate.md +++ b/docs/man/policy/kas-registry/public-keys/deactivate.md @@ -11,10 +11,10 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Deactivate a public key. ## Example ```shell -otdfctl policy kas-registry public-key get --id=62857b55-560c-4b67-96e3-33e4670ecb3b +otdfctl policy kas-registry public-keys deactivate --id=62857b55-560c-4b67-96e3-33e4670ecb3b ``` diff --git a/docs/man/policy/kas-registry/public-keys/get.md b/docs/man/policy/kas-registry/public-keys/get.md index e97c20c6..2feffc02 100644 --- a/docs/man/policy/kas-registry/public-keys/get.md +++ b/docs/man/policy/kas-registry/public-keys/get.md @@ -11,7 +11,7 @@ command: required: true --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Get a public key. ## Example diff --git a/docs/man/policy/kas-registry/public-keys/list-mappings.md b/docs/man/policy/kas-registry/public-keys/list-mappings.md index 6286b46d..ba74aa9b 100644 --- a/docs/man/policy/kas-registry/public-keys/list-mappings.md +++ b/docs/man/policy/kas-registry/public-keys/list-mappings.md @@ -19,10 +19,31 @@ command: description: Offset (page) quantity from start of the list --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +List public key mappings shows a list of Key Access Servers and associated public keys. Each Key also has a list of namespaces, attribute defnitions and attribute values that are associated with it. ## Example ```shell -otdfctl policy kas-registry list +# List public key mappings +otdfctl policy kas-registry public-keys list-mappings +``` + +```shell +# List public key mappings with Key Access Server ID +otdfctl policy kas-registry public-keys list-mappings --kas=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# List public key mappings with Key Access Server Name +otdfctl policy kas-registry public-keys list-mappings --kas=example-kas +``` + +```shell +# List public key mappings with Key Access Server URI +otdfctl policy kas-registry public-keys list-mappings --kas=https://example.com/kas +``` + +```shell +# List public key mappings with Public Key ID +otdfctl policy kas-registry public-keys list-mappings --public-key-id=62857b55-560c-4b67-96e3-33e4670ecb3b ``` diff --git a/docs/man/policy/kas-registry/public-keys/list.md b/docs/man/policy/kas-registry/public-keys/list.md index 27737c53..93a5eb3a 100644 --- a/docs/man/policy/kas-registry/public-keys/list.md +++ b/docs/man/policy/kas-registry/public-keys/list.md @@ -19,10 +19,25 @@ command: description: Offset (page) quantity from start of the list --- -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +List public keys shows a list of public keys. ## Example ```shell otdfctl policy kas-registry public-key list ``` + +```shell +# List public keys with Key Access Server ID +otdfctl policy kas-registry public-keys list --kas=62857b55-560c-4b67-96e3-33e4670ecb3b +``` + +```shell +# List public keys with Key Access Server Name +otdfctl policy kas-registry public-keys list --kas=example-kas +``` + +```shell +# List public keys with Key Access Server URI +otdfctl policy kas-registry public-keys list --kas=https://example.com/kas +``` \ No newline at end of file diff --git a/docs/man/policy/kas-registry/public-keys/mappings/_index.md b/docs/man/policy/kas-registry/public-keys/mappings/_index.md deleted file mode 100644 index e1fd945c..00000000 --- a/docs/man/policy/kas-registry/public-keys/mappings/_index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Manage Public Key Mappings -command: - name: mappings - aliases: - - mp - - map ---- \ No newline at end of file diff --git a/docs/man/policy/kas-registry/public-keys/unsafe/_index.md b/docs/man/policy/kas-registry/public-keys/unsafe/_index.md index 21269c1a..56e0bc1b 100644 --- a/docs/man/policy/kas-registry/public-keys/unsafe/_index.md +++ b/docs/man/policy/kas-registry/public-keys/unsafe/_index.md @@ -8,12 +8,4 @@ command: required: false --- -Unsafe changes are dangerous mutations to Policy that can significantly change access behavior around existing attributes -and entitlement. - -Depending on the unsafe change introduced and already existing TDFs, TDFs might become inaccessible that were previously -accessible or vice versa. - -Make sure you know what you are doing. - -For more general information about attributes, see the `attributes` subcommand. +Unsafe changes to a Public Key are potentially dangerous or more restrictive than the default behavior. Use this command with caution. diff --git a/docs/man/policy/kas-registry/public-keys/unsafe/delete.md b/docs/man/policy/kas-registry/public-keys/unsafe/delete.md index cb4ac612..2a1c65ad 100644 --- a/docs/man/policy/kas-registry/public-keys/unsafe/delete.md +++ b/docs/man/policy/kas-registry/public-keys/unsafe/delete.md @@ -16,18 +16,10 @@ command: description: Force deletion without interactive confirmation (dangerous) --- -Removes knowledge of a KAS (registration) from a platform's policy. - -If resource data has been TDFd utilizing key splits from the registered KAS, deletion from -the registry (and therefore any associated grants) may prevent decryption depending on the -type of grants and relevant key splits. - -Make sure you know what you are doing. - -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Delete a Key Access Server Public Key. ## Example ```shell -otdfctl policy kas-registry delete --id 3c39618a-cd8c-48cf-a60c-e8a2f4be4dd5 +otdfctl policy kas-registry public-keys unsafe delete --id=62857b55-560c-4b67-96e3-33e4670ecb3b ``` diff --git a/docs/man/policy/kas-registry/public-keys/update.md b/docs/man/policy/kas-registry/public-keys/update.md index 1dea392b..2b580753 100644 --- a/docs/man/policy/kas-registry/public-keys/update.md +++ b/docs/man/policy/kas-registry/public-keys/update.md @@ -18,18 +18,11 @@ command: default: false --- -Update the `uri`, `metadata`, or key material (remote/cached) for a KAS registered to the platform. - -If resource data has been TDFd utilizing key splits from the registered KAS, deletion from -the registry (and therefore any associated grants) may prevent decryption depending on the -type of grants and relevant key splits. - -Make sure you know what you are doing. - -For more information about registration of Key Access Servers, see the manual for `kas-registry`. +Update the metadata of a public key. The public key information itself cannot be updated. To update a public key create a new key with the updated information. ## Example ```shell -otdfctl policy kas-registry update --id 3c39618a-cd8c-48cf-a60c-e8a2f4be4dd5 --name example-kas2-newname --public-key-remote "https://example.com/kas2/new_public_key" +otdfctl policy kas-registry public-key update --id=62857b55-560c-4b67-96e3-33e4670ecb3b --label key=value ``` + From 247a4bc297d612a994e8bb4a4abda1ddb1483d97 Mon Sep 17 00:00:00 2001 From: Sean Trantalis Date: Tue, 11 Feb 2025 13:08:12 -0500 Subject: [PATCH 3/7] implement list operations on keys per policy resource --- cmd/policy-attributeNamespaces.go | 64 ++++++++++++++- cmd/policy-attributeValues.go | 64 ++++++++++++++- cmd/policy-attributes.go | 64 ++++++++++++++- docs/man/policy/attributes/keys/list.md | 3 + .../policy/attributes/namespaces/keys/list.md | 3 + .../man/policy/attributes/values/keys/list.md | 3 + e2e/attributes.bats | 79 +++++++++++++++++++ e2e/namespaces.bats | 43 ++++++++++ pkg/handlers/attribute.go | 4 - pkg/handlers/attributeValues.go | 4 - pkg/handlers/namespaces.go | 4 - 11 files changed, 320 insertions(+), 15 deletions(-) diff --git a/cmd/policy-attributeNamespaces.go b/cmd/policy-attributeNamespaces.go index 4132c8c9..a4a7961b 100644 --- a/cmd/policy-attributeNamespaces.go +++ b/cmd/policy-attributeNamespaces.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/charmbracelet/lipgloss" "github.com/evertras/bubble-table/table" "github.com/opentdf/otdfctl/pkg/cli" "github.com/opentdf/otdfctl/pkg/man" @@ -311,7 +312,62 @@ func policy_NamespaceKeysRemove(cmd *cobra.Command, args []string) { HandleSuccess(cmd, "Public key removed from namespace", t, nil) } -func policy_NamespaceKeysListcmd(cmd *cobra.Command, args []string) {} + +func policy_NamespaceKeysListcmd(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + ns := c.Flags.GetRequiredString("namespace") + showPublicKey := c.Flags.GetOptionalBool("show-public-key") + + list, err := h.GetNamespace(ns) + if err != nil { + cli.ExitWithError("Failed to list namespace keys", err) + } + + columns := []table.Column{ + table.NewFlexColumn("kas_name", "KAS Name", cli.FlexColumnWidthThree), + table.NewFlexColumn("kas_uri", "KAS URI", cli.FlexColumnWidthThree), + table.NewFlexColumn("kid", "Key ID", cli.FlexColumnWidthThree), + table.NewFlexColumn("alg", "Algorithm", cli.FlexColumnWidthThree), + } + + if showPublicKey { + columns = append(columns, table.NewFlexColumn("public_key", "Public Key", cli.FlexColumnWidthFour)) + } + + t := cli.NewTable(columns...) + rows := []table.Row{} + for _, key := range list.GetKeys() { + + alg, err := enumToAlg(key.GetPublicKey().GetAlg()) + if err != nil { + cli.ExitWithError("Failed to get algorithm", err) + } + + rowStyle := lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()) + + if key.GetIsActive().GetValue() { + rowStyle = rowStyle.Background(cli.ColorGreen.Background) + } else { + rowStyle = rowStyle.Background(cli.ColorRed.Background) + } + + rd := table.RowData{ + "key_id": key.GetPublicKey().GetKid(), + "algorithm": alg, + "is_active": key.GetIsActive().GetValue(), + "kas_id": key.GetKas().GetId(), + "kas_name": key.GetKas().GetName(), + "kas_uri": key.GetKas().GetUri(), + "public_key": key.GetPublicKey().GetPem(), + } + rows = append(rows, table.NewRow(rd).WithStyle(rowStyle)) + } + t = t.WithRows(rows) + HandleSuccess(cmd, "", t, list) +} func init() { getCmd := man.Docs.GetCommand("policy/attributes/namespaces/get", @@ -456,6 +512,12 @@ func init() { namespaceKeysListDoc.GetDocFlag("namespace").Default, namespaceKeysListDoc.GetDocFlag("namespace").Description, ) + namespaceKeysListDoc.Flags().BoolP( + namespaceKeysListDoc.GetDocFlag("show-public-key").Name, + namespaceKeysListDoc.GetDocFlag("show-public-key").Shorthand, + namespaceKeysListDoc.GetDocFlag("show-public-key").DefaultAsBool(), + namespaceKeysListDoc.GetDocFlag("show-public-key").Description, + ) policy_NamespaceKeysCmd.AddSubcommands(namespaceKeysAddDoc, namespaceKeysRemoveDoc, namespaceKeysListDoc) policy_attributeNamespacesCmd.AddSubcommands(getCmd, listCmd, createDoc, updateCmd, deactivateCmd, unsafeCmd, policy_NamespaceKeysCmd) diff --git a/cmd/policy-attributeValues.go b/cmd/policy-attributeValues.go index ef06bf2e..c90a8d17 100644 --- a/cmd/policy-attributeValues.go +++ b/cmd/policy-attributeValues.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "github.com/charmbracelet/lipgloss" "github.com/evertras/bubble-table/table" "github.com/opentdf/otdfctl/pkg/cli" "github.com/opentdf/otdfctl/pkg/man" @@ -274,7 +275,62 @@ func policy_ValueKeysRemove(cmd *cobra.Command, args []string) { HandleSuccess(cmd, "Public key removed from value", t, nil) } -func policy_ValueKeysList(cmd *cobra.Command, args []string) {} + +func policy_ValueKeysList(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + ns := c.Flags.GetRequiredString("value") + showPublicKey := c.Flags.GetOptionalBool("show-public-key") + + list, err := h.GetAttributeValue(ns) + if err != nil { + cli.ExitWithError("Failed to list attribute value keys", err) + } + + columns := []table.Column{ + table.NewFlexColumn("kas_name", "KAS Name", cli.FlexColumnWidthThree), + table.NewFlexColumn("kas_uri", "KAS URI", cli.FlexColumnWidthThree), + table.NewFlexColumn("kid", "Key ID", cli.FlexColumnWidthThree), + table.NewFlexColumn("alg", "Algorithm", cli.FlexColumnWidthThree), + } + + if showPublicKey { + columns = append(columns, table.NewFlexColumn("public_key", "Public Key", cli.FlexColumnWidthFour)) + } + + t := cli.NewTable(columns...) + rows := []table.Row{} + for _, key := range list.GetKeys() { + + alg, err := enumToAlg(key.GetPublicKey().GetAlg()) + if err != nil { + cli.ExitWithError("Failed to get algorithm", err) + } + + rowStyle := lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()) + + if key.GetIsActive().GetValue() { + rowStyle = rowStyle.Background(cli.ColorGreen.Background) + } else { + rowStyle = rowStyle.Background(cli.ColorRed.Background) + } + + rd := table.RowData{ + "key_id": key.GetPublicKey().GetKid(), + "algorithm": alg, + "is_active": key.GetIsActive().GetValue(), + "kas_id": key.GetKas().GetId(), + "kas_name": key.GetKas().GetName(), + "kas_uri": key.GetKas().GetUri(), + "public_key": key.GetPublicKey().GetPem(), + } + rows = append(rows, table.NewRow(rd).WithStyle(rowStyle)) + } + t = t.WithRows(rows) + HandleSuccess(cmd, "", t, list) +} func init() { createCmd := man.Docs.GetCommand("policy/attributes/values/create", @@ -426,6 +482,12 @@ func init() { valueKeysListDoc.GetDocFlag("value").Default, valueKeysListDoc.GetDocFlag("value").Description, ) + valueKeysListDoc.Flags().BoolP( + valueKeysListDoc.GetDocFlag("show-public-key").Name, + valueKeysListDoc.GetDocFlag("show-public-key").Shorthand, + valueKeysListDoc.GetDocFlag("show-public-key").DefaultAsBool(), + valueKeysListDoc.GetDocFlag("show-public-key").Description, + ) policy_ValueKeysCmd.AddSubcommands(valueKeysAddDoc, valueKeysRemoveDoc, valueKeysListDoc) diff --git a/cmd/policy-attributes.go b/cmd/policy-attributes.go index 267c2910..07c26ebb 100644 --- a/cmd/policy-attributes.go +++ b/cmd/policy-attributes.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "github.com/charmbracelet/lipgloss" "github.com/evertras/bubble-table/table" "github.com/opentdf/otdfctl/pkg/cli" "github.com/opentdf/otdfctl/pkg/handlers" @@ -346,7 +347,62 @@ func policy_DefinitionKeysRemove(cmd *cobra.Command, args []string) { HandleSuccess(cmd, "Public key removed from definition", t, nil) } -func policy_DefinitionKeysList(cmd *cobra.Command, args []string) {} + +func policy_DefinitionKeysList(cmd *cobra.Command, args []string) { + c := cli.New(cmd, args) + h := NewHandler(c) + defer h.Close() + + ns := c.Flags.GetRequiredString("definition") + showPublicKey := c.Flags.GetOptionalBool("show-public-key") + + list, err := h.GetAttribute(ns) + if err != nil { + cli.ExitWithError("Failed to list definition keys", err) + } + + columns := []table.Column{ + table.NewFlexColumn("kas_name", "KAS Name", cli.FlexColumnWidthThree), + table.NewFlexColumn("kas_uri", "KAS URI", cli.FlexColumnWidthThree), + table.NewFlexColumn("kid", "Key ID", cli.FlexColumnWidthThree), + table.NewFlexColumn("alg", "Algorithm", cli.FlexColumnWidthThree), + } + + if showPublicKey { + columns = append(columns, table.NewFlexColumn("public_key", "Public Key", cli.FlexColumnWidthFour)) + } + + t := cli.NewTable(columns...) + rows := []table.Row{} + for _, key := range list.GetKeys() { + + alg, err := enumToAlg(key.GetPublicKey().GetAlg()) + if err != nil { + cli.ExitWithError("Failed to get algorithm", err) + } + + rowStyle := lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()) + + if key.GetIsActive().GetValue() { + rowStyle = rowStyle.Background(cli.ColorGreen.Background) + } else { + rowStyle = rowStyle.Background(cli.ColorRed.Background) + } + + rd := table.RowData{ + "key_id": key.GetPublicKey().GetKid(), + "algorithm": alg, + "is_active": key.GetIsActive().GetValue(), + "kas_id": key.GetKas().GetId(), + "kas_name": key.GetKas().GetName(), + "kas_uri": key.GetKas().GetUri(), + "public_key": key.GetPublicKey().GetPem(), + } + rows = append(rows, table.NewRow(rd).WithStyle(rowStyle)) + } + t = t.WithRows(rows) + HandleSuccess(cmd, "", t, list) +} func init() { // Create an attribute @@ -527,6 +583,12 @@ func init() { definitionKeysListDoc.GetDocFlag("definition").Default, definitionKeysListDoc.GetDocFlag("definition").Description, ) + definitionKeysListDoc.Flags().BoolP( + definitionKeysListDoc.GetDocFlag("show-public-key").Name, + definitionKeysListDoc.GetDocFlag("show-public-key").Shorthand, + definitionKeysListDoc.GetDocFlag("show-public-key").DefaultAsBool(), + definitionKeysListDoc.GetDocFlag("show-public-key").Description, + ) policy_DefinitionKeysCmd.AddSubcommands(definitionKeysAddDoc, definitionKeysRemoveDoc, definitionKeysListDoc) diff --git a/docs/man/policy/attributes/keys/list.md b/docs/man/policy/attributes/keys/list.md index ea5d5c8c..0966a6ec 100644 --- a/docs/man/policy/attributes/keys/list.md +++ b/docs/man/policy/attributes/keys/list.md @@ -9,6 +9,9 @@ command: shorthand: d description: ID or FQN of the Attribute Definition required: true + - name: show-public-key + description: Show the public key + default: false --- List the public key mappings for an attribute definition. diff --git a/docs/man/policy/attributes/namespaces/keys/list.md b/docs/man/policy/attributes/namespaces/keys/list.md index 40644d4e..d9dd0cc2 100644 --- a/docs/man/policy/attributes/namespaces/keys/list.md +++ b/docs/man/policy/attributes/namespaces/keys/list.md @@ -9,6 +9,9 @@ command: shorthand: n description: ID or FQN of the Attribute Namespace required: true + - name: show-public-key + description: Show the public key + default: false --- List the public key mappings for an attribute namespace. diff --git a/docs/man/policy/attributes/values/keys/list.md b/docs/man/policy/attributes/values/keys/list.md index f57f1164..296b16ba 100644 --- a/docs/man/policy/attributes/values/keys/list.md +++ b/docs/man/policy/attributes/values/keys/list.md @@ -9,6 +9,9 @@ command: shorthand: v description: ID or FQN of the Attribute Value required: true + - name: show-public-key + description: Show the public key + default: false --- List the public key mappings for an attribute value. diff --git a/e2e/attributes.bats b/e2e/attributes.bats index 69945e29..16b50904 100755 --- a/e2e/attributes.bats +++ b/e2e/attributes.bats @@ -263,6 +263,40 @@ teardown_file() { echo "$output" | jq -e 'has("keys") | not' || fail "KAS ID still present" } +@test "list_keys_on_definition" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Add the key to the attribute definition + log_info "Running ${run_otdfctl_attr} keys add --definition $ATTR_ID --public-key-id $PUBLIC_KEY_ID" + run_otdfctl_attr keys add --definition "$ATTR_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # List the keys on the attribute definition + log_info "Running ${run_otdfctl_attr} keys list --definition $ATTR_ID" + run_otdfctl_attr keys list --definition "$ATTR_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -r '.keys[].id' | while read -r id; do + log_debug "Checking PK ID: $id against $PUBLIC_KEY_ID" + [ "$id" = "$PUBLIC_KEY_ID" ] || fail "KAS ID does not match" + done +} + @test "add_remove_key_to_value" { log_info "Starting test: $BATS_TEST_NAME" @@ -326,4 +360,49 @@ teardown_file() { assert_success echo "$output" | jq -e 'has("keys") | not' || fail "KAS ID still present" +} + +@test "list_keys_on_value" { + log_info "Starting test: $BATS_TEST_NAME" + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Add value to the attribute definition + log_info "Running ${run_otdfctl_attr} values create --attribute-id $ATTR_ID --value val1" + run_otdfctl_attr values create --attribute-id "$ATTR_ID" --value val1 --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + VALUE_ID=$(echo "$output" | jq -r '.id') + + # Add the key to the attribute value + log_info "Running ${run_otdfctl_attr} values keys add --value $VALUE_ID --public-key-id $KID" + run_otdfctl_attr values keys add --value "$VALUE_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # List the keys on the attribute value + log_info "Running ${run_otdfctl_attr} values keys list --value $VALUE_ID" + run_otdfctl_attr values keys list --value "$VALUE_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -r '.[].id' | while read -r id; do + log_debug "Checking PK ID: $id against $PUBLIC_KEY_ID" + [ "$id" = "$PUBLIC_KEY_ID" ] || fail "KAS ID does not match" + done } \ No newline at end of file diff --git a/e2e/namespaces.bats b/e2e/namespaces.bats index cac9f564..b68f2e8f 100755 --- a/e2e/namespaces.bats +++ b/e2e/namespaces.bats @@ -261,4 +261,47 @@ teardown_file() { echo "$output" | jq -e 'has("keys") | not' || fail "KAS ID still present" run_otdfctl_ns unsafe delete --id $NAMESPACE_ID --force +} + +@test "list_keys_on_namesapces" { + log_info "Starting test: $BATS_TEST_NAME" + + run_otdfctl_ns create --name keys.test --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + NAMESPACE_ID=$(echo "$output" | jq -r '.id') + + create_kas "$KAS_URI" "$KAS_NAME" + + ALG="rsa:2048" + KID="test" + + create_public_key "$KAS_ID" "$KID" "$ALG" + + # Add the key to the attribute namespace + log_info "Running ${run_otdfctl_ns} keys add --value $NAMESPACE_ID --public-key-id $KID" + run_otdfctl_ns keys add --namespace "$NAMESPACE_ID" --public-key-id "$PUBLIC_KEY_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + # Check that the key was added to the attribute namespace + log_info "Running ${run_otdfctl_ns} get --id $NAMESPACE_ID" + run_otdfctl_ns get --id "$NAMESPACE_ID" --json + + log_debug "Raw output:" + log_debug "$output" + + assert_success + + echo "$output" | jq -r '.keys[].id' | while read -r id; do + log_debug "Checking PK ID: $id against $PUBLIC_KEY_ID" + [ "$id" = "$PUBLIC_KEY_ID" ] || fail "KAS ID does not match" + done } \ No newline at end of file diff --git a/pkg/handlers/attribute.go b/pkg/handlers/attribute.go index d60980b6..3398d221 100644 --- a/pkg/handlers/attribute.go +++ b/pkg/handlers/attribute.go @@ -253,7 +253,3 @@ func (h Handler) RemovePublicKeyFromDefinition(ctx context.Context, definition, } return ak, nil } - -func (h Handler) ListPublicKeyDefinitionMappings(ctx context.Context, definitionID string, offset, limit int32) error { - return nil -} diff --git a/pkg/handlers/attributeValues.go b/pkg/handlers/attributeValues.go index 309704ef..ca5e77ca 100644 --- a/pkg/handlers/attributeValues.go +++ b/pkg/handlers/attributeValues.go @@ -168,7 +168,3 @@ func (h Handler) RemovePublicKeyFromValue(ctx context.Context, value, publicKeyI } return vk, nil } - -func (h Handler) ListPublicKeyValueMappings(ctx context.Context, attributeID string, offset, limit int32) error { - return nil -} diff --git a/pkg/handlers/namespaces.go b/pkg/handlers/namespaces.go index 0d1df891..eafaf110 100644 --- a/pkg/handlers/namespaces.go +++ b/pkg/handlers/namespaces.go @@ -170,7 +170,3 @@ func (h Handler) RemovePublicKeyFromNamespace(ctx context.Context, nameSpace, pu return nk, nil } - -func (h Handler) ListPublicKeyNamespaceMappings(ctx context.Context, namespaceID string, offset, limit int32) error { - return nil -} From 6447a1f05551b7fd5d965be5f588b4a055de12b2 Mon Sep 17 00:00:00 2001 From: Sean Trantalis Date: Tue, 11 Feb 2025 19:55:20 -0500 Subject: [PATCH 4/7] revert changes of removing kas-registry create flags --- cmd/kas-registry.go | 83 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/cmd/kas-registry.go b/cmd/kas-registry.go index fe72c620..9fb2912b 100644 --- a/cmd/kas-registry.go +++ b/cmd/kas-registry.go @@ -1,6 +1,9 @@ package cmd import ( + "encoding/json" + "encoding/pem" + "errors" "fmt" "github.com/evertras/bubble-table/table" @@ -8,6 +11,7 @@ import ( "github.com/opentdf/otdfctl/pkg/man" "github.com/opentdf/platform/protocol/go/policy" "github.com/spf13/cobra" + "google.golang.org/protobuf/encoding/protojson" ) var policy_kasRegistryCmd = man.Docs.GetCommand("policy/kas-registry") @@ -25,9 +29,16 @@ func policy_getKeyAccessRegistry(cmd *cobra.Command, args []string) { cli.ExitWithError(errMsg, err) } + key := &policy.PublicKey{} + key.PublicKey = &policy.PublicKey_Cached{Cached: kas.GetPublicKey().GetCached()} + if kas.GetPublicKey().GetRemote() != "" { + key.PublicKey = &policy.PublicKey_Remote{Remote: kas.GetPublicKey().GetRemote()} + } + rows := [][]string{ {"Id", kas.GetId()}, {"URI", kas.GetUri()}, + {"PublicKey", kas.GetPublicKey().String()}, } name := kas.GetName() if name != "" { @@ -62,10 +73,17 @@ func policy_listKeyAccessRegistries(cmd *cobra.Command, args []string) { ) rows := []table.Row{} for _, kas := range list { + key := policy.PublicKey{} + key.PublicKey = &policy.PublicKey_Cached{Cached: kas.GetPublicKey().GetCached()} + if kas.GetPublicKey().GetRemote() != "" { + key.PublicKey = &policy.PublicKey_Remote{Remote: kas.GetPublicKey().GetRemote()} + } + rows = append(rows, table.NewRow(table.RowData{ "id": kas.GetId(), "uri": kas.GetUri(), "name": kas.GetName(), + "pk": kas.GetPublicKey().String(), })) } t = t.WithRows(rows) @@ -79,12 +97,27 @@ func policy_createKeyAccessRegistry(cmd *cobra.Command, args []string) { defer h.Close() uri := c.Flags.GetRequiredString("uri") + cachedJSON := c.Flags.GetOptionalString("public-keys") + remote := c.Flags.GetOptionalString("public-key-remote") name := c.Flags.GetOptionalString("name") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) + key := new(policy.PublicKey) + if cachedJSON != "" { + if remote != "" { + cli.ExitWithError("Found values for both flags 'public-keys' and 'public-key-remote'", errors.New("error: only one public key is allowed")) + } + err := unmarshalKASPublicKey(cachedJSON, key) + if err != nil { + cli.ExitWithError(fmt.Sprintf("KAS registry key is invalid: '%s', see help for examples", cachedJSON), err) + } + } else { + key.PublicKey = &policy.PublicKey_Remote{Remote: remote} + } + created, err := h.CreateKasRegistryEntry( uri, - &policy.PublicKey{}, + key, name, getMetadataMutable(metadataLabels), ) @@ -95,6 +128,7 @@ func policy_createKeyAccessRegistry(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", created.GetId()}, {"URI", created.GetUri()}, + {"PublicKey", created.GetPublicKey().String()}, } if name != "" { rows = append(rows, []string{"Name", name}) @@ -115,18 +149,34 @@ func policy_updateKeyAccessRegistry(cmd *cobra.Command, args []string) { id := c.Flags.GetRequiredID("id") uri := c.Flags.GetOptionalString("uri") name := c.Flags.GetOptionalString("name") + cachedJSON := c.Flags.GetOptionalString("public-keys") + remote := c.Flags.GetOptionalString("public-key-remote") metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0}) - allEmpty := len(metadataLabels) == 0 && uri == "" && name == "" + allEmpty := cachedJSON == "" && remote == "" && len(metadataLabels) == 0 && uri == "" && name == "" if allEmpty { cli.ExitWithError("No values were passed to update. Please pass at least one value to update (E.G. 'uri', 'name', 'public-keys', 'public-key-remote', 'label')", nil) } + pubKey := new(policy.PublicKey) + if cachedJSON != "" && remote != "" { + e := fmt.Errorf("only one public key is allowed. Please pass either a cached or remote public key but not both") + cli.ExitWithError("Issue with update flags 'public-keys' and 'public-key-remote': ", e) + } + if cachedJSON != "" { + err := unmarshalKASPublicKey(cachedJSON, pubKey) + if err != nil { + cli.ExitWithError(fmt.Sprintf("KAS registry key is invalid: '%s', see help for examples", cachedJSON), err) + } + } else if remote != "" { + pubKey.PublicKey = &policy.PublicKey_Remote{Remote: remote} + } + updated, err := h.UpdateKasRegistryEntry( id, uri, name, - &policy.PublicKey{}, + pubKey, getMetadataMutable(metadataLabels), getMetadataUpdateBehavior(), ) @@ -136,6 +186,7 @@ func policy_updateKeyAccessRegistry(cmd *cobra.Command, args []string) { rows := [][]string{ {"Id", id}, {"URI", updated.GetUri()}, + {"PublicKey", updated.GetPublicKey().String()}, } if updated.GetName() != "" { rows = append(rows, []string{"Name", updated.GetName()}) @@ -177,6 +228,32 @@ func policy_deleteKeyAccessRegistry(cmd *cobra.Command, args []string) { HandleSuccess(cmd, kas.GetId(), t, kas) } +// TODO: remove this when the data is structured +func unmarshalKASPublicKey(keyStr string, key *policy.PublicKey) error { + if !json.Valid([]byte(keyStr)) { + return errors.New("invalid JSON") + } + + if err := protojson.Unmarshal([]byte(keyStr), key); err != nil { + return errors.New("invalid shape") + } + + // Validate all PEMs + keyErrs := []error{} + for i, k := range key.GetCached().GetKeys() { + block, _ := pem.Decode([]byte(k.GetPem())) + if block == nil { + keyErrs = append(keyErrs, fmt.Errorf("error in key[%d] with KID \"%s\": PEM is invalid", i, k.GetKid())) + } + } + + if len(keyErrs) != 0 { + return errors.Join(keyErrs...) + } + + return nil +} + func init() { getDoc := man.Docs.GetCommand("policy/kas-registry/get", man.WithRun(policy_getKeyAccessRegistry), From 89a30ab32b231475f7ec67b7c1c7265c4b4a5037 Mon Sep 17 00:00:00 2001 From: strantalis Date: Fri, 14 Feb 2025 07:25:29 -0500 Subject: [PATCH 5/7] bump to protocol v0.2.26 --- cmd/kas-public-keys.go | 10 +- .../policy/kas-registry/public-keys/create.md | 6 +- e2e/helpers.bash | 181 +++++++++--------- go.mod | 6 +- go.sum | 2 + 5 files changed, 101 insertions(+), 104 deletions(-) diff --git a/cmd/kas-public-keys.go b/cmd/kas-public-keys.go index b2e454b2..5420fa2f 100644 --- a/cmd/kas-public-keys.go +++ b/cmd/kas-public-keys.go @@ -373,6 +373,11 @@ func formatAssociations(assocs []*kasregistry.ListPublicKeyMappingResponse_Assoc return strings.Join(fqns, "\n") } +func isValidBase64(s string) bool { + _, err := base64.StdEncoding.DecodeString(s) + return err == nil +} + func parseAndFormatKey(key string) (string, error) { if key == "" { return "", errors.New("key is required") @@ -395,11 +400,6 @@ func parseAndFormatKey(key string) (string, error) { return key, nil } -func isValidBase64(s string) bool { - _, err := base64.StdEncoding.DecodeString(s) - return err == nil -} - func enumToAlg(enum policy.KasPublicKeyAlgEnum) (string, error) { switch enum { case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048: diff --git a/docs/man/policy/kas-registry/public-keys/create.md b/docs/man/policy/kas-registry/public-keys/create.md index f0a5c82b..3fde7a04 100644 --- a/docs/man/policy/kas-registry/public-keys/create.md +++ b/docs/man/policy/kas-registry/public-keys/create.md @@ -16,14 +16,15 @@ command: - name: key-id shorthand: i description: ID of the public key. + required: true - name: algorithm shorthand: a description: Algorithm of the public key. (rsa:2048, rsa:4096, ec:secp256r1, ec:secp384r1, ec:secp521r1) + required: true - name: label description: "Optional metadata 'labels' in the format: key=value" shorthand: l - default: '' - + default: "" --- Add a public key to a Key Access Server. The public key must be in PEM format. It can be base64 encoded or plain text. @@ -48,4 +49,3 @@ create --kas kas-1 --key-id key-1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q= otdfctl policy kas-registry public-key create --kas https://example.com/kas --key-id key-1 --key "-----BEGIN CERTIFICATE-----\nMIIB...5Q=\n-----END CERTIFICATE-----\n" --algorithm rsa:2048 ``` - diff --git a/e2e/helpers.bash b/e2e/helpers.bash index 8c158a16..a0e913e9 100644 --- a/e2e/helpers.bash +++ b/e2e/helpers.bash @@ -2,11 +2,11 @@ #!/usr/bin/env bash # OTDFCTL Helper Functions -run_otdfctl_kasr () { +run_otdfctl_kasr() { run sh -c "./otdfctl policy kas-registry $HOST $WITH_CREDS $*" } -create_kas () { +create_kas() { log_debug "Creating KAS... $1 $2" run_otdfctl_kasr create --uri "$1" -n "$2" --json @@ -27,30 +27,30 @@ create_public_key() { # Select the appropriate key generation function based on the algorithm case "$algorithm" in - "$RSA_2048_ALG") - eval "$(gen_rsa_2048)" - key_content="$RSA_2048_PUBLIC_KEY" - ;; - "$RSA_4096_ALG") - eval "$(gen_rsa_4096)" - key_content="$RSA_4096_PUBLIC_KEY" - ;; - "$EC_256_ALG") - eval "$(gen_ec256)" - key_content="$EC_256_PUBLIC_KEY" - ;; - "$EC_384_ALG") - eval "$(gen_ec384)" - key_content="$EC_384_PUBLIC_KEY" - ;; - "$EC_521_ALG") - eval "$(gen_ec521)" - key_content="$EC_521_PUBLIC_KEY" - ;; - *) - log_error "Unsupported algorithm: $algorithm" - return 1 - ;; + "$RSA_2048_ALG") + eval "$(gen_rsa_2048)" + key_content="$RSA_2048_PUBLIC_KEY" + ;; + "$RSA_4096_ALG") + eval "$(gen_rsa_4096)" + key_content="$RSA_4096_PUBLIC_KEY" + ;; + "$EC_256_ALG") + eval "$(gen_ec256)" + key_content="$EC_256_PUBLIC_KEY" + ;; + "$EC_384_ALG") + eval "$(gen_ec384)" + key_content="$EC_384_PUBLIC_KEY" + ;; + "$EC_521_ALG") + eval "$(gen_ec521)" + key_content="$EC_521_PUBLIC_KEY" + ;; + *) + log_error "Unsupported algorithm: $algorithm" + return 1 + ;; esac # Verify key content is not empty @@ -83,48 +83,48 @@ create_public_key() { } # Setup Helper -setup_helper(){ - load "${BATS_LIB_PATH}/bats-support/load.bash" - load "${BATS_LIB_PATH}/bats-assert/load.bash" - - # Initialize IDs to empty strings in case creation fails - KAS_ID="" - PUBLIC_KEY_ID="" - PUBLIC_KEY_IDS=() # Initialize an empty array - - KAS_URI="https://testing-public-key.io" - KAS_NAME="public-key-kas" - - RSA_2048_ALG="rsa:2048" - RSA_4096_ALG="rsa:4096" - EC_256_ALG="ec:secp256r1" - EC_384_ALG="ec:secp384r1" - EC_521_ALG="ec:secp521r1" +setup_helper() { + load "${BATS_LIB_PATH}/bats-support/load.bash" + load "${BATS_LIB_PATH}/bats-assert/load.bash" + + # Initialize IDs to empty strings in case creation fails + KAS_ID="" + PUBLIC_KEY_ID="" + PUBLIC_KEY_IDS=() # Initialize an empty array + + KAS_URI="https://testing-public-key.io" + KAS_NAME="public-key-kas" + + RSA_2048_ALG="rsa:2048" + RSA_4096_ALG="rsa:4096" + EC_256_ALG="ec:secp256r1" + EC_384_ALG="ec:secp384r1" + EC_521_ALG="ec:secp521r1" } # Cleanup Helper -cleanup_helper(){ - # Iterate over the array of public key IDs and delete them - for PUBLIC_KEY_ID in "${PUBLIC_KEY_IDS[@]}"; do - if [ -n "$PUBLIC_KEY_ID" ]; then - log_debug "Running ${run_otdfctl_kasr} public-key unsafe delete --id $PUBLIC_KEY_ID --force --json" - run_otdfctl_kasr public-key unsafe delete --id "$PUBLIC_KEY_ID" --force --json - log_debug "$output" - if [ $? -ne 0 ]; then - log_info "Error: Failed to delete public key with ID: $PUBLIC_KEY_ID" - fi - log_debug "Deleted public key with ID: $PUBLIC_KEY_ID" - fi - done - if [ -n "$KAS_ID" ]; then - log_debug "Running ${run_otdfctl_kasr} delete --id $KAS_ID --force --json" - run_otdfctl_kasr delete --id "$KAS_ID" --force --json - log_debug "$output" - if [ $? -ne 0 ]; then - log_info "Error: Failed to delete KAS registry with ID: $KAS_ID" - fi - log_debug "Deleted KAS registry with ID: $KAS_ID" - fi +cleanup_helper() { + # Iterate over the array of public key IDs and delete them + for PUBLIC_KEY_ID in "${PUBLIC_KEY_IDS[@]}"; do + if [ -n "$PUBLIC_KEY_ID" ]; then + log_debug "Running ${run_otdfctl_kasr} public-key unsafe delete --id $PUBLIC_KEY_ID --force --json" + run_otdfctl_kasr public-key unsafe delete --id "$PUBLIC_KEY_ID" --force --json + log_debug "$output" + if [ $? -ne 0 ]; then + log_info "Error: Failed to delete public key with ID: $PUBLIC_KEY_ID" + fi + log_debug "Deleted public key with ID: $PUBLIC_KEY_ID" + fi + done + if [ -n "$KAS_ID" ]; then + log_debug "Running ${run_otdfctl_kasr} delete --id $KAS_ID --force --json" + run_otdfctl_kasr delete --id "$KAS_ID" --force --json + log_debug "$output" + if [ $? -ne 0 ]; then + log_info "Error: Failed to delete KAS registry with ID: $KAS_ID" + fi + log_debug "Deleted KAS registry with ID: $KAS_ID" + fi } # Helper function for debug logging @@ -143,7 +143,7 @@ log_info() { gen_rsa_2048() { log_debug "Generating RSA 2048 key pair" local private_key public_key - + # Generate private key private_key=$(openssl genrsa 2048) @@ -158,7 +158,7 @@ gen_rsa_2048() { gen_rsa_4096() { log_debug "Generating RSA 4096 key pair" local private_key public_key - + # Generate private key private_key=$(openssl genrsa 4096) @@ -170,45 +170,42 @@ gen_rsa_4096() { # Helper function to generate an EC 256 key pair gen_ec256() { - log_debug "Generating EC 256 key pair" - local private_key public_key + log_debug "Generating EC 256 key pair" + local private_key public_key - # Generate private key - private_key=$(openssl ecparam -name prime256v1 -genkey) + # Generate private key + private_key=$(openssl ecparam -name prime256v1 -genkey) + + # Extract public key + public_key=$(echo "$private_key" | openssl ec -pubout) - # Extract public key - public_key=$(echo "$private_key" | openssl ec -pubout) - - printf 'export EC_256_PUBLIC_KEY=%q\n' "$public_key" + printf 'export EC_256_PUBLIC_KEY=%q\n' "$public_key" } # Helper function to generate an EC 384 key pair gen_ec384() { - log_debug "Generating EC 384 key pair" - local private_key public_key + log_debug "Generating EC 384 key pair" + local private_key public_key - # Generate private key - private_key=$(openssl ecparam -name secp384r1 -genkey) + # Generate private key + private_key=$(openssl ecparam -name secp384r1 -genkey) - # Extract public key - public_key=$(echo "$private_key" | openssl ec -pubout) + # Extract public key + public_key=$(echo "$private_key" | openssl ec -pubout) - printf 'export EC_384_PUBLIC_KEY=%q\n' "$public_key" + printf 'export EC_384_PUBLIC_KEY=%q\n' "$public_key" } # Helper function to generate an EC 521 key pair gen_ec521() { - log_debug "Generating EC 521 key pair" - local private_key public_key + log_debug "Generating EC 521 key pair" + local private_key public_key - # Generate private key - private_key=$(openssl ecparam -name secp521r1 -genkey) + # Generate private key + private_key=$(openssl ecparam -name secp521r1 -genkey) - # Extract public key - public_key=$(echo "$private_key" | openssl ec -pubout) + # Extract public key + public_key=$(echo "$private_key" | openssl ec -pubout) - printf 'export EC_521_PUBLIC_KEY=%q\n' "$public_key" + printf 'export EC_521_PUBLIC_KEY=%q\n' "$public_key" } - - - diff --git a/go.mod b/go.mod index 9094a8d6..40a34533 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/opentdf/otdfctl go 1.22.7 -replace github.com/opentdf/platform/protocol/go => ../platform/protocol/go - require ( github.com/adrg/frontmatter v0.2.0 github.com/charmbracelet/bubbles v0.18.0 @@ -11,6 +9,7 @@ require ( github.com/charmbracelet/glamour v0.8.0 github.com/charmbracelet/huh v0.5.2 github.com/charmbracelet/lipgloss v1.0.0 + github.com/charmbracelet/x/term v0.2.1 github.com/creasty/defaults v1.8.0 github.com/evertras/bubble-table v0.17.1 github.com/gabriel-vasile/mimetype v1.4.8 @@ -18,7 +17,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 github.com/opentdf/platform/lib/flattening v0.1.3 - github.com/opentdf/platform/protocol/go v0.2.24 + github.com/opentdf/platform/protocol/go v0.2.26 github.com/opentdf/platform/sdk v0.3.25 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -42,7 +41,6 @@ require ( github.com/catppuccin/go v0.2.0 // indirect github.com/charmbracelet/x/ansi v0.4.5 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect diff --git a/go.sum b/go.sum index 552853b6..7ff542e0 100644 --- a/go.sum +++ b/go.sum @@ -228,6 +228,8 @@ github.com/opentdf/platform/lib/flattening v0.1.3 h1:IuOm/wJVXNrzOV676Ticgr0wyBk github.com/opentdf/platform/lib/flattening v0.1.3/go.mod h1:Gs/T+6FGZKk9OAdz2Jf1R8CTGeNRYrq1lZGDeYT3hrY= github.com/opentdf/platform/lib/ocrypto v0.1.7 h1:IcCYRrwmMqntqUE8frmUDg5EZ0WMdldpGeGhbv9+/A8= github.com/opentdf/platform/lib/ocrypto v0.1.7/go.mod h1:4bhKPbRFzURMerH5Vr/LlszHvcoXQbfJXa0bpY7/7yg= +github.com/opentdf/platform/protocol/go v0.2.26 h1:22ugJFhAjlz7BRAky3eBljIQrsLzmsdkKVM+pjuG09k= +github.com/opentdf/platform/protocol/go v0.2.26/go.mod h1:eldxqX2oF2ADtG8ivhfwn1lALVMX4aaUM+Lp9ynOJXs= github.com/opentdf/platform/sdk v0.3.25 h1:dZEVeWKfbjrnEXKzSado8ebpzIrk2n6R7RSZRbX+FwE= github.com/opentdf/platform/sdk v0.3.25/go.mod h1:F+RGbT2o9GlzWH9s8VkZyUNUEEAWA3V2RSs8jNQHbqM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= From 380ea3ef3d58ae089c6beefdc9516b2c9607ac76 Mon Sep 17 00:00:00 2001 From: strantalis Date: Fri, 14 Feb 2025 10:56:47 -0500 Subject: [PATCH 6/7] fix lint issues --- cmd/kas-public-keys.go | 21 +++++++++++---------- cmd/policy-attributeNamespaces.go | 1 - cmd/policy-attributeValues.go | 1 - cmd/policy-attributes.go | 1 - pkg/handlers/kas-public-keys.go | 4 +++- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/kas-public-keys.go b/cmd/kas-public-keys.go index 5420fa2f..27548891 100644 --- a/cmd/kas-public-keys.go +++ b/cmd/kas-public-keys.go @@ -270,10 +270,10 @@ func policy_listPublicKeyMappings(cmd *cobra.Command, args []string) { } t := cli.NewTable( - table.NewColumn("kas_name", "KAS Name", 20), - table.NewColumn("kas_uri", "KAS URI", 30), - table.NewColumn("key_count", "Key Count", 10), - table.NewFlexColumn("publicKeys", "Public Keys", 1), + table.NewFlexColumn("kas_name", "KAS Name", cli.FlexColumnWidthTwo), + table.NewFlexColumn("kas_uri", "KAS URI", cli.FlexColumnWidthThree), + table.NewFlexColumn("key_count", "Key Count", cli.FlexColumnWidthOne), + table.NewFlexColumn("publicKeys", "Public Keys", cli.FlexColumnWidthFour), ) rows := []table.Row{} @@ -301,9 +301,9 @@ func policy_listPublicKeyMappings(cmd *cobra.Command, args []string) { func createPublicKeysTable(keys []*kasregistry.ListPublicKeyMappingResponse_PublicKey, termWidth int) string { // Create columns for the nested table columns := []table.Column{ - table.NewColumn("kid", "KID", 8), - table.NewColumn("algorithm", "Algorithm", 12), - table.NewColumn("active", "Active", 6), + table.NewFlexColumn("kid", "KID", cli.FlexColumnWidthTwo), + table.NewFlexColumn("algorithm", "Algorithm", cli.FlexColumnWidthTwo), + table.NewFlexColumn("active", "Active", cli.FlexColumnWidthOne), table.NewFlexColumn("namespaces", "Namespaces", cli.FlexColumnWidthTwo), table.NewFlexColumn("definitions", "Definitions", cli.FlexColumnWidthThree), table.NewFlexColumn("values", "Values", cli.FlexColumnWidthFour), @@ -336,14 +336,15 @@ func createPublicKeysTable(keys []*kasregistry.ListPublicKeyMappingResponse_Publ } minWidth := 80 // Set a minimum width for the nested table - tableWidth := int(float64(termWidth) * 0.75) + tableWidthPercentage := 0.75 + tableWidth := int(float64(termWidth) * tableWidthPercentage) if tableWidth < minWidth { tableWidth = minWidth } // Create nested table nestedTable := table.New(columns). WithRows(rows). - WithTargetWidth(int(float64(tableWidth) * 0.75)). + WithTargetWidth(int(float64(tableWidth) * tableWidthPercentage)). WithMultiline(true). WithNoPagination(). BorderRounded(). @@ -401,7 +402,7 @@ func parseAndFormatKey(key string) (string, error) { } func enumToAlg(enum policy.KasPublicKeyAlgEnum) (string, error) { - switch enum { + switch enum { //nolint:exhaustive // UNSPECIFIED is not needed here case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048: return "rsa:2048", nil case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096: diff --git a/cmd/policy-attributeNamespaces.go b/cmd/policy-attributeNamespaces.go index a4a7961b..ca4183a6 100644 --- a/cmd/policy-attributeNamespaces.go +++ b/cmd/policy-attributeNamespaces.go @@ -340,7 +340,6 @@ func policy_NamespaceKeysListcmd(cmd *cobra.Command, args []string) { t := cli.NewTable(columns...) rows := []table.Row{} for _, key := range list.GetKeys() { - alg, err := enumToAlg(key.GetPublicKey().GetAlg()) if err != nil { cli.ExitWithError("Failed to get algorithm", err) diff --git a/cmd/policy-attributeValues.go b/cmd/policy-attributeValues.go index c90a8d17..7240c96c 100644 --- a/cmd/policy-attributeValues.go +++ b/cmd/policy-attributeValues.go @@ -303,7 +303,6 @@ func policy_ValueKeysList(cmd *cobra.Command, args []string) { t := cli.NewTable(columns...) rows := []table.Row{} for _, key := range list.GetKeys() { - alg, err := enumToAlg(key.GetPublicKey().GetAlg()) if err != nil { cli.ExitWithError("Failed to get algorithm", err) diff --git a/cmd/policy-attributes.go b/cmd/policy-attributes.go index 07c26ebb..bedb24a5 100644 --- a/cmd/policy-attributes.go +++ b/cmd/policy-attributes.go @@ -375,7 +375,6 @@ func policy_DefinitionKeysList(cmd *cobra.Command, args []string) { t := cli.NewTable(columns...) rows := []table.Row{} for _, key := range list.GetKeys() { - alg, err := enumToAlg(key.GetPublicKey().GetAlg()) if err != nil { cli.ExitWithError("Failed to get algorithm", err) diff --git a/pkg/handlers/kas-public-keys.go b/pkg/handlers/kas-public-keys.go index 9f5b5823..94e90947 100644 --- a/pkg/handlers/kas-public-keys.go +++ b/pkg/handlers/kas-public-keys.go @@ -11,6 +11,8 @@ import ( "google.golang.org/grpc/status" ) +const maxKeyIDLength = 32 + func (h Handler) CreatePublicKey(kas, pk, kid, alg string, metadata *common.MetadataMutable) (*policy.Key, error) { // Check if alg is valid algEnum, err := algToEnum(alg) @@ -19,7 +21,7 @@ func (h Handler) CreatePublicKey(kas, pk, kid, alg string, metadata *common.Meta } // Key ID can't be more than 32 characters - if len(kid) > 32 { + if len(kid) > maxKeyIDLength { return nil, errors.New("key id must be less than 32 characters") } From 418b695be73f420cff1398da55577cc668a8cf1a Mon Sep 17 00:00:00 2001 From: strantalis Date: Fri, 14 Feb 2025 12:21:02 -0500 Subject: [PATCH 7/7] update docs --- docs/man/policy/kas-registry/public-keys/create.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/policy/kas-registry/public-keys/create.md b/docs/man/policy/kas-registry/public-keys/create.md index 3fde7a04..fa0b7275 100644 --- a/docs/man/policy/kas-registry/public-keys/create.md +++ b/docs/man/policy/kas-registry/public-keys/create.md @@ -19,7 +19,7 @@ command: required: true - name: algorithm shorthand: a - description: Algorithm of the public key. (rsa:2048, rsa:4096, ec:secp256r1, ec:secp384r1, ec:secp521r1) + description: Algorithm of the public key. (rsa:2048, rsa:4096, ec:p256, ec:p384, ec:p521) required: true - name: label description: "Optional metadata 'labels' in the format: key=value"