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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions otdfctl/cmd/policy/kasKeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,7 @@ func initKASKeysCommands() {
createDoc.GetDocFlag("private-key-pem").Description,
)
injectLabelFlags(&createDoc.Command, false)
createDoc.MarkSensitiveFlags()

// Get Kas Key
getDoc := man.Docs.GetCommand("policy/kas-registry/key/get",
Expand Down Expand Up @@ -995,6 +996,7 @@ func initKASKeysCommands() {
rotateDoc.GetDocFlag("private-key-pem").Description,
)
injectLabelFlags(&rotateDoc.Command, true)
rotateDoc.MarkSensitiveFlags()

// Import Kas Key
importDoc := man.Docs.GetCommand("policy/kas-registry/key/import",
Expand Down Expand Up @@ -1049,6 +1051,7 @@ func initKASKeysCommands() {
importDoc.GetDocFlag("legacy").Description,
)
injectLabelFlags(&importDoc.Command, false)
importDoc.MarkSensitiveFlags()

mappingsDoc := man.Docs.GetCommand("policy/kas-registry/key/list-mappings",
man.WithRun(policyListKeyMappings),
Expand Down
2 changes: 2 additions & 0 deletions otdfctl/docs/man/policy/kas-registry/key/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ command:
description: Identifier related to the wrapping key. Its meaning depends on the `mode`. For `local` mode, it's a descriptive ID for the `wrappingKey` you provide. For `provider` or `remote` mode, it's the ID of the key within the external provider/system used for wrapping.
- name: wrapping-key
shorthand: w
sensitive: true
description: The symmetric key material (AES cipher, hex encoded) used to wrap the generated private key. Primarily used when `mode` is `local`.
- name: private-key-pem
sensitive: true
description: The private key PEM (encrypted by an AES 32-byte key, then base64 encoded). Used when importing an existing key pair, typically with `provider` mode.
- name: provider-config-id
shorthand: p
Expand Down
2 changes: 2 additions & 0 deletions otdfctl/docs/man/policy/kas-registry/key/import.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ command:
required: true
- name: wrapping-key
shorthand: w
sensitive: true
description: The symmetric key material (AES cipher, hex encoded) used to wrap the imported private key.
required: true
- name: private-key-pem
sensitive: true
description: The base64 encoded private key PEM to import
required: true
- name: public-key-pem
Expand Down
6 changes: 4 additions & 2 deletions otdfctl/docs/man/policy/kas-registry/key/rotate.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ command:
description: Identifier related to the wrapping key. Its meaning depends on the `mode`. For `local` mode, it's a descriptive ID for the `wrappingKey` you provide. For `provider` or `remote` mode, it's the ID of the key within the external provider/system used for wrapping.
- name: wrapping-key
shorthand: w
description: The symmetric key material (AES cipher, base64 encoded) used to wrap the generated private key. Primarily used when `mode` is `local`.
sensitive: true
description: The symmetric key material (AES cipher, hex encoded) used to wrap the generated private key. Primarily used when `mode` is `local`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: private-key-pem
sensitive: true
description: The private key PEM (encrypted by an AES 32-byte key, then base64 encoded). Used when importing an existing key pair, typically with `provider` mode.
- name: provider-config-id
shorthand: p
Expand All @@ -54,7 +56,7 @@ This command replaces an existing key with a new one while maintaining reference
Rotate an existing key to a new key in local mode, where the KAS generates the key pair and the private key is wrapped by the provided `wrappingKey`:

```shell
otdfctl policy kas-registry key rotate --key "old-key-id" --kas "https://kas.example.com/kas" --key-id "new-key-v2" --algorithm "rsa:2048" --mode "local" --wrapping-key-id "virtru-stored-key" --wrapping-key "YWVzIGtleQ=="
otdfctl policy kas-registry key rotate --key "old-key-id" --kas "https://kas.example.com/kas" --key-id "new-key-v2" --algorithm "rsa:2048" --mode "local" --wrapping-key-id "virtru-stored-key" --wrapping-key "a8c4824daafcfa38ed0d13002e92b08720e6c4fcee67d52e954c1a6e045907d1"
```

### Rotate a key in `provider` mode
Expand Down
19 changes: 19 additions & 0 deletions otdfctl/pkg/man/docflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ import (
"github.com/opentdf/platform/otdfctl/pkg/cli"
)

// SensitiveAnnotationKey is the pflag annotation key used to mark flags whose
// values contain secrets (cryptographic keys, tokens, etc.) and must not appear
// in logs or process listings.
const SensitiveAnnotationKey = "sensitive"

type DocFlag struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Shorthand string `yaml:"shorthand"`
Default string `yaml:"default"`
Enum []string `yaml:"enum"`
Sensitive bool `yaml:"sensitive"`
}

func (d *Doc) GetDocFlag(name string) DocFlag {
Expand All @@ -29,3 +35,16 @@ func (d *Doc) GetDocFlag(name string) DocFlag {
func (f DocFlag) DefaultAsBool() bool {
return f.Default == "true"
}

// MarkSensitiveFlags sets pflag annotations on all flags in the command's
// FlagSet that are marked sensitive in the doc metadata. Call after all
// flags have been registered.
func (d *Doc) MarkSensitiveFlags() {
for _, df := range d.DocFlags {
if df.Sensitive {
if err := d.Flags().SetAnnotation(df.Name, SensitiveAnnotationKey, []string{"true"}); err != nil {
panic(fmt.Sprintf("failed to mark flag %q as sensitive for command %q: %v", df.Name, d.Use, err))
}
}
}
}
78 changes: 78 additions & 0 deletions otdfctl/pkg/man/docflags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package man

import (
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDocFlagSensitiveParsing(t *testing.T) {
doc, err := ProcessDoc(`---
title: Test Command
command:
name: test
flags:
- name: wrapping-key
sensitive: true
description: A sensitive flag
- name: algorithm
description: A non-sensitive flag
---

Test doc body.
`)
require.NoError(t, err)
require.Len(t, doc.DocFlags, 2)

wk := doc.GetDocFlag("wrapping-key")
assert.True(t, wk.Sensitive)

alg := doc.GetDocFlag("algorithm")
assert.False(t, alg.Sensitive)
}

func TestMarkSensitiveFlags(t *testing.T) {
doc, err := ProcessDoc(`---
title: Test Command
command:
name: test
flags:
- name: wrapping-key
sensitive: true
description: Sensitive
- name: name
description: Not sensitive
---

Body.
`)
require.NoError(t, err)

doc.Flags().String("wrapping-key", "", "Sensitive")
doc.Flags().String("name", "", "Not sensitive")

doc.MarkSensitiveFlags()

wkFlag := doc.Flags().Lookup("wrapping-key")
require.NotNil(t, wkFlag)
assert.Equal(t, []string{"true"}, wkFlag.Annotations[SensitiveAnnotationKey])

nameFlag := doc.Flags().Lookup("name")
require.NotNil(t, nameFlag)
assert.Nil(t, nameFlag.Annotations)
}

func TestMarkSensitiveFlagsPanicsOnUnregistered(t *testing.T) {
doc := &Doc{
Command: cobra.Command{Use: "test"},
DocFlags: []DocFlag{
{Name: "missing-flag", Sensitive: true},
},
}

assert.Panics(t, func() {
doc.MarkSensitiveFlags()
})
}
Loading