Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 28 additions & 26 deletions cmd/base-keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import (
"errors"
"fmt"

"github.com/evertras/bubble-table/table"
"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/opentdf/otdfctl/pkg/utils"
"github.com/opentdf/platform/protocol/go/policy/kasregistry"
"github.com/spf13/cobra"
)
Expand All @@ -27,34 +29,34 @@
var policyKasRegistryBaseKeysCmd *cobra.Command

func getKasKeyIdentifier(c *cli.Cli) (*kasregistry.KasKeyIdentifier, error) {
keyID := c.Flags.GetOptionalString("keyId")
kasID := c.Flags.GetOptionalString("kasId")
kasName := c.Flags.GetOptionalString("kasName")
kasURI := c.Flags.GetOptionalString("kasUri")

var identifier *kasregistry.KasKeyIdentifier
if keyID != "" {
identifier = &kasregistry.KasKeyIdentifier{
Kid: keyID,
}
switch {
case kasID != "":
identifier.Identifier = &kasregistry.KasKeyIdentifier_KasId{
KasId: kasID,
}
case kasName != "":
identifier.Identifier = &kasregistry.KasKeyIdentifier_Name{
Name: kasName,
}
case kasURI != "":
identifier.Identifier = &kasregistry.KasKeyIdentifier_Uri{
Uri: kasURI,
}
default:
return nil, errors.New("at least one of 'kasId', 'kasName', or 'kasUri' must be provided with 'keyId'")
}
// This function is called when the user provides a human-readable key ID
// via the --key flag and identifies the KAS it belongs to via the --kas flag.
humanReadableKeyID := c.Flags.GetRequiredString("key") // Flag for the key's human-readable ID
kasIdentifierInput := c.Flags.GetRequiredString("kas") // Flag for KAS ID, URI, or Name

// Basic validation, though GetRequiredString should handle empty inputs.
if humanReadableKeyID == "" {
return nil, errors.New("--key (human-readable key ID) cannot be empty")
}
if kasIdentifierInput == "" {
return nil, errors.New("--kas (KAS identifier) cannot be empty")
}

identifier := &kasregistry.KasKeyIdentifier{
Kid: humanReadableKeyID,
}

kasInputType := utils.ClassifyString(kasIdentifierInput)
switch kasInputType {

Check failure on line 50 in cmd/base-keys.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type utils.IdentifierStringType: utils.StringTypeUnknown (exhaustive)
case utils.StringTypeUUID:
identifier.Identifier = &kasregistry.KasKeyIdentifier_KasId{KasId: kasIdentifierInput}
case utils.StringTypeURI:
identifier.Identifier = &kasregistry.KasKeyIdentifier_Uri{Uri: kasIdentifierInput}
case utils.StringTypeGeneric:
identifier.Identifier = &kasregistry.KasKeyIdentifier_Name{Name: kasIdentifierInput}
default: // Catches StringTypeUnknown and any other unexpected types
return nil, fmt.Errorf("invalid KAS identifier: '%s'. Must be a KAS UUID, URI, or Name", kasIdentifierInput)
}
return identifier, nil
}

Expand Down
87 changes: 54 additions & 33 deletions cmd/kas-keys.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"encoding/base64"
"errors"
"fmt"
Expand All @@ -10,6 +11,7 @@
"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/handlers"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/opentdf/otdfctl/pkg/utils"
"github.com/opentdf/platform/lib/ocrypto"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -243,10 +245,8 @@
h := NewHandler(c)
defer h.Close()

keyID := c.Flags.GetRequiredString("keyId")
kasID := c.Flags.GetOptionalString("kasId")
kasURI := c.Flags.GetOptionalString("kasUri")
kasName := c.Flags.GetOptionalString("kasName")
keyIdentifier := c.Flags.GetRequiredString("key")
kasIdentifier := c.Flags.GetRequiredString("kas")
metadataLabels = c.Flags.GetStringSlice("label", metadataLabels, cli.FlagsStringSliceOptions{Min: 0})

alg, err := algToEnum(c.Flags.GetRequiredString("alg"))
Expand All @@ -259,23 +259,23 @@
cli.ExitWithError("Invalid mode", err)
}

wrappingKeyID = c.Flags.GetOptionalString("wrappingKeyId")
wrappingKeyID = c.Flags.GetOptionalString("wrapping-key-id")
if mode != policy.KeyMode_KEY_MODE_PUBLIC_KEY_ONLY && wrappingKeyID == "" {
formattedMode, _ := enumToMode(mode)
cli.ExitWithError(fmt.Sprintf("wrappingKeyId is required for mode %s", formattedMode), nil)
cli.ExitWithError(fmt.Sprintf("wrapping-key-id is required for mode %s", formattedMode), nil)
}

providerConfigID = c.Flags.GetOptionalString("providerConfigId")
providerConfigID = c.Flags.GetOptionalString("provider-config-id")
if (mode == policy.KeyMode_KEY_MODE_PROVIDER_ROOT_KEY || mode == policy.KeyMode_KEY_MODE_REMOTE) && providerConfigID == "" {
formattedMode, _ := enumToMode(mode)
cli.ExitWithError(fmt.Sprintf("providerConfigId is required for mode %s", formattedMode), nil)
cli.ExitWithError(fmt.Sprintf("provider-config-id is required for mode %s", formattedMode), nil)
}

var publicKeyCtx *policy.PublicKeyCtx
var privateKeyCtx *policy.PrivateKeyCtx
switch mode {
case policy.KeyMode_KEY_MODE_CONFIG_ROOT_KEY:
wrappingKey := c.Flags.GetRequiredString("wrappingKey")
wrappingKey := c.Flags.GetRequiredString("wrapping-key")
privateKeyPem, publicKeyPem, err := generateKeys(alg)
if err != nil {
cli.ExitWithError("Failed to generate keys", err)
Expand All @@ -296,9 +296,9 @@
WrappedKey: privPemBase64,
}
case policy.KeyMode_KEY_MODE_PROVIDER_ROOT_KEY:
providerConfigID = c.Flags.GetRequiredString("providerConfigId")
publicPem := c.Flags.GetRequiredString("pubPem")
privatePem := c.Flags.GetRequiredString("privatePem")
providerConfigID = c.Flags.GetRequiredString("provider-config-id")
publicPem := c.Flags.GetRequiredString("public-key-pem")
privatePem := c.Flags.GetRequiredString("private-key-pem")
_, err = base64.StdEncoding.DecodeString(publicPem)
if err != nil {
cli.ExitWithError("pem must be base64 encoded", err)
Expand All @@ -315,8 +315,8 @@
WrappedKey: privatePem,
}
case policy.KeyMode_KEY_MODE_REMOTE:
pem := c.Flags.GetRequiredString("pubPem")
providerConfigID = c.Flags.GetRequiredString("providerConfigId")
pem := c.Flags.GetRequiredString("public-key-pem")
providerConfigID = c.Flags.GetRequiredString("provider-config-pem")

_, err = base64.StdEncoding.DecodeString(pem)
if err != nil {
Expand All @@ -330,7 +330,7 @@
KeyId: wrappingKeyID,
}
case policy.KeyMode_KEY_MODE_PUBLIC_KEY_ONLY:
pem := c.Flags.GetRequiredString("pubPem")
pem := c.Flags.GetRequiredString("public-key-pem")
_, err = base64.StdEncoding.DecodeString(pem)
if err != nil {
cli.ExitWithError("pem must be base64 encoded", err)
Expand All @@ -344,21 +344,15 @@
cli.ExitWithError("Invalid mode", nil)
}

if kasID == "" {
kas, err := h.GetKasRegistryEntry(c.Context(), handlers.KasIdentifier{
Name: kasName,
URI: kasURI,
})
if err != nil {
cli.ExitWithError("Failed to get kas registry entry", err)
}
kasID = kas.GetId()
kasIdentifier, err = resolveKasIdentifier(c.Context(), kasIdentifier, h)
if err != nil {
cli.ExitWithError("Invalid kas identifier", err)
}

kasKey, err := h.CreateKasKey(
c.Context(),
kasID,
keyID,
kasIdentifier,
keyIdentifier,
alg,
mode,
publicKeyCtx,
Expand Down Expand Up @@ -440,7 +434,7 @@

limit := c.Flags.GetRequiredInt32("limit")
offset := c.Flags.GetRequiredInt32("offset")
algArg := c.Flags.GetOptionalString("alg")
algArg := c.Flags.GetOptionalString("algorithm")
var alg policy.Algorithm
if algArg != "" {
var err error
Expand All @@ -449,15 +443,16 @@
cli.ExitWithError("Invalid algorithm", err)
}
}
kasID := c.Flags.GetOptionalString("kasId")
kasName := c.Flags.GetOptionalString("kasName")
kasURI := c.Flags.GetOptionalString("kasUri")
kasIdentifier := c.Flags.GetRequiredString("kas")

kasIdentifier, err := resolveKasIdentifier(c.Context(), kasIdentifier, h)
if err != nil {
cli.ExitWithError("Invalid kas identifier", err)
}

// Get the list of keys.
keys, page, err := h.ListKasKeys(c.Context(), limit, offset, alg, handlers.KasIdentifier{
ID: kasID,
Name: kasName,
URI: kasURI,
ID: kasIdentifier,
})
if err != nil {
cli.ExitWithError("Failed to list kas keys", err)
Expand Down Expand Up @@ -529,6 +524,32 @@
HandleSuccess(cmd, "", t, keys)
}

func resolveKasIdentifier(ctx context.Context, ident string, h handlers.Handler) (string, error) {
// Use the ClassifyString helper to determine how to look up the KAS
kasLookup := handlers.KasIdentifier{}
kasInputType := utils.ClassifyString(ident)

switch kasInputType {

Check failure on line 532 in cmd/kas-keys.go

View workflow job for this annotation

GitHub Actions / lint

missing cases in switch of type utils.IdentifierStringType: utils.StringTypeUnknown (exhaustive)
case utils.StringTypeUUID:
return ident, nil
case utils.StringTypeURI:
kasLookup.URI = ident
case utils.StringTypeGeneric:
kasLookup.Name = ident
default:
return "", errors.New("invalid kas identifier")
}

if kasInputType != utils.StringTypeUUID {
resolvedKas, err := h.GetKasRegistryEntry(ctx, kasLookup)
if err != nil {
return "", errors.Join(errors.New("failed to get kas registry entry"), err)
}
return resolvedKas.GetId(), nil
}
return "", nil
}

func init() {
// Create Kas Key
createDoc := man.Docs.GetCommand("policy/kas-registry/key/create",
Expand Down
6 changes: 4 additions & 2 deletions docs/man/policy/kas-registry/key/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ command:
- k
flags:
- name: json
description: output single command in JSON (overrides configured output format)
description: Output the result of a subcommand in JSON format (overrides configured output format). This is an inherited flag.
default: 'false'
---

Set of commands for managing KAS keys used with OpenTDF platform.
Provides a set of subcommands for managing cryptographic keys within the Key Access Server (KAS) registry.
These keys are essential for encryption and decryption operations within the OpenTDF platform.
Operations include creating, retrieving, listing, updating, and managing the platform's base key.
12 changes: 6 additions & 6 deletions docs/man/policy/kas-registry/key/base/_index.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
---
title: Base Key Operations
title: Platform Base Key Management

command:
name: base
hidden: true
aliases:
- k
flags:
- name: json
description: output single command in JSON (overrides configured output format)
description: Output the result of a subcommand in JSON format (overrides configured output format). This is an inherited flag.
default: 'false'
---

Set of operations to be used for setting and getting base platform keys.
These base platform keys will be used to encrypt data in the following cases:
Provides subcommands for managing the platform's base cryptographic key.
This base key is a fallback used for encryption operations in specific scenarios:

- No attributes present when encrypting a file
- No keys associated with an attribute

Available operations include `get` to retrieve the current base key and `set` to designate a new base key.
7 changes: 5 additions & 2 deletions docs/man/policy/kas-registry/key/base/get.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ title: Get Base Key
command:
name: get
aliases:
- g
- g
---

Command for getting the platform base key.
Command for retrieving information about the currently configured platform base key. This key is used for encryption operations when no attributes are present or when attributes lack associated keys.

The command will display details such as the key's identifier (KeyID or UUID) and the Key Access Server (KAS) it is registered with.

## Examples

Retrieve the platform base key information in the default (human-readable) format:
```
otdfctl policy kas-registry key base get
```
27 changes: 10 additions & 17 deletions docs/man/policy/kas-registry/key/base/set.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,23 @@ command:
aliases:
- s
flags:
- name: id
shorthand: i
description: ID of the key to retrieve
- name: keyId
- name: key
shorthand: k
description: KeyID of the key to retrieve
- name: kasUri
shorthand: u
description: URI of the Key access server that the key is assigned to.
- name: kasName
shorthand: n
description: Name of the Key access server that the key is assigned to.
- name: kasId
shorthand: d
description: Id of the Key access server that the key is assigned to.
description: The KeyID (human-readable identifier) or the internal UUID of an existing key within the specified KAS. This key will be designated as the platform base key. The system will attempt to resolve the provided value as either a UUID or a KeyID.
required: true
- name: kas
description: Specify the Key Access Server (KAS) where the key (identified by `--key`) is registered. The KAS can be identified by its ID, URI, or Name.
required: true

---

Command for setting a base key to be used for encryption operations on data where no attributes are present or where no keys are present on found attributes.
Command for setting a base key to be used for encryption operations on data where no attributes are present or where no keys are present on found attributes. The key to be set as the base key must be identified using its KeyID or UUID via the `--key` flag, and the KAS it belongs to must be specified with the `--kas` flag.

## Examples

Set the platform base key using the internal UUID of a key from a KAS specified by its URI:
```
otdfctl policy kas-registry key base set --id 8af2059f-5d0b-46c2-84f0-bed8a6101d90
otdfctl policy kas-registry key base set --key 8af2059f-5d0b-46c2-84f0-bed8a6101d90 --kas https://kas.example.com/kas

otdfctl policy kas-registry key base set --kasUri
otdfctl policy kas-registry key base set --key my-platform-base-key-v1 --kas primary-key-access-server
```
Loading
Loading