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
170 changes: 169 additions & 1 deletion cmd/policy-attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/evertras/bubble-table/table"
"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/handlers"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -184,6 +185,117 @@ func policy_updateAttribute(cmd *cobra.Command, args []string) {
}
}

func policy_unsafeReactivateAttribute(cmd *cobra.Command, args []string) {
h := NewHandler(cmd)
defer h.Close()

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

a, err := h.GetAttribute(id)
if err != nil {
errMsg := fmt.Sprintf("Failed to get attribute (%s)", id)
cli.ExitWithError(errMsg, err)
}

if !forceUnsafe {
cli.ConfirmTextInput(cli.ActionReactivate, "attribute", cli.InputNameFQN, a.GetFqn())
}

if a, err := h.UnsafeReactivateAttribute(id); err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to reactivate attribute (%s)", id), err)
} else {
rows := [][]string{
{"Id", a.Id},
{"Name", a.Name},
}
if mdRows := getMetadataRows(a.Metadata); mdRows != nil {
rows = append(rows, mdRows...)
}
t := cli.NewTabular(rows...)
HandleSuccess(cmd, id, t, a)
}
}

func policy_unsafeUpdateAttribute(cmd *cobra.Command, args []string) {
h := NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
id := flagHelper.GetRequiredString("id")
name := flagHelper.GetOptionalString("name")
rule := flagHelper.GetOptionalString("rule")
valuesOrder := flagHelper.GetStringSlice("values-order", attrValues, cli.FlagHelperStringSliceOptions{})

a, err := h.GetAttribute(id)
if err != nil {
errMsg := fmt.Sprintf("Failed to get attribute (%s)", id)
cli.ExitWithError(errMsg, err)
}

if !forceUnsafe {
cli.ConfirmTextInput(cli.ActionUpdateUnsafe, "attribute", cli.InputNameFQN, a.GetFqn())
}

if err := h.UnsafeUpdateAttribute(id, name, rule, valuesOrder); err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to update attribute (%s)", id), err)
} else {
var (
values []string
valueIDs []string
)
for _, v := range a.GetValues() {
values = append(values, v.GetValue())
valueIDs = append(valueIDs, v.GetId())
}
rows := [][]string{
{"Id", a.Id},
{"Name", a.GetName()},
{"Rule", handlers.GetAttributeRuleFromAttributeType(a.GetRule())},
{"Values", cli.CommaSeparated(values)},
{"Value IDs", cli.CommaSeparated(valueIDs)},
}
if mdRows := getMetadataRows(a.Metadata); mdRows != nil {
rows = append(rows, mdRows...)
}
t := cli.NewTabular(rows...)
HandleSuccess(cmd, id, t, a)
}
}

func policy_unsafeDeleteAttribute(cmd *cobra.Command, args []string) {
h := NewHandler(cmd)
defer h.Close()

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

a, err := h.GetAttribute(id)
if err != nil {
errMsg := fmt.Sprintf("Failed to get attribute (%s)", id)
cli.ExitWithError(errMsg, err)
}

if !forceUnsafe {
cli.ConfirmTextInput(cli.ActionDelete, "attribute", cli.InputNameFQN, a.GetFqn())
}

if err := h.UnsafeDeleteAttribute(id, a.GetFqn()); err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to delete attribute (%s)", id), err)
} else {
rows := [][]string{
{"Deleted", "true"},
{"Id", a.Id},
{"Name", a.Name},
}
if mdRows := getMetadataRows(a.Metadata); mdRows != nil {
rows = append(rows, mdRows...)
}
t := cli.NewTabular(rows...)
HandleSuccess(cmd, id, t, a)
}
}

func init() {
// Create an attribute
createDoc := man.Docs.GetCommand("policy/attributes/create",
Expand Down Expand Up @@ -261,6 +373,62 @@ func init() {
deactivateDoc.GetDocFlag("id").Description,
)

policy_attributesCmd.AddSubcommands(createDoc, getDoc, listDoc, updateDoc, deactivateDoc)
// unsafe actions on attributes
unsafeCmd := man.Docs.GetCommand("policy/attributes/unsafe")
unsafeCmd.PersistentFlags().BoolVar(&forceUnsafe,
unsafeCmd.GetDocFlag("force").Name,
false,
unsafeCmd.GetDocFlag("force").Description,
)

reactivateCmd := man.Docs.GetCommand("policy/attributes/unsafe/reactivate",
man.WithRun(policy_unsafeReactivateAttribute),
)
reactivateCmd.Flags().StringP(
reactivateCmd.GetDocFlag("id").Name,
reactivateCmd.GetDocFlag("id").Shorthand,
reactivateCmd.GetDocFlag("id").Default,
reactivateCmd.GetDocFlag("id").Description,
)
deleteCmd := man.Docs.GetCommand("policy/attributes/unsafe/delete",
man.WithRun(policy_unsafeDeleteAttribute),
)
deleteCmd.Flags().StringP(
deleteCmd.GetDocFlag("id").Name,
deleteCmd.GetDocFlag("id").Shorthand,
deleteCmd.GetDocFlag("id").Default,
deleteCmd.GetDocFlag("id").Description,
)
unsafeUpdateCmd := man.Docs.GetCommand("policy/attributes/unsafe/update",
man.WithRun(policy_unsafeUpdateAttribute),
)
unsafeUpdateCmd.Flags().StringP(
unsafeUpdateCmd.GetDocFlag("id").Name,
unsafeUpdateCmd.GetDocFlag("id").Shorthand,
unsafeUpdateCmd.GetDocFlag("id").Default,
unsafeUpdateCmd.GetDocFlag("id").Description,
)
unsafeUpdateCmd.Flags().StringP(
unsafeUpdateCmd.GetDocFlag("name").Name,
unsafeUpdateCmd.GetDocFlag("name").Shorthand,
unsafeUpdateCmd.GetDocFlag("name").Default,
unsafeUpdateCmd.GetDocFlag("name").Description,
)
unsafeUpdateCmd.Flags().StringP(
unsafeUpdateCmd.GetDocFlag("rule").Name,
unsafeUpdateCmd.GetDocFlag("rule").Shorthand,
unsafeUpdateCmd.GetDocFlag("rule").Default,
unsafeUpdateCmd.GetDocFlag("rule").Description,
)
unsafeUpdateCmd.Flags().StringSliceVarP(
&attrValues,
unsafeUpdateCmd.GetDocFlag("values-order").Name,
unsafeUpdateCmd.GetDocFlag("values-order").Shorthand,
[]string{},
unsafeUpdateCmd.GetDocFlag("values-order").Description,
)

unsafeCmd.AddSubcommands(reactivateCmd, deleteCmd, unsafeUpdateCmd)
policy_attributesCmd.AddSubcommands(createDoc, getDoc, listDoc, updateDoc, deactivateDoc, unsafeCmd)
policyCmd.AddCommand(&policy_attributesCmd.Command)
}
6 changes: 3 additions & 3 deletions docs/man/policy/attributes/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ command:
required: true
- name: value
shorthand: v
description: Value of the attribute
description: Value of the attribute (i.e. 'value1')
required: true
- name: namespace
shorthand: s
description: Namespace of the attribute
description: Namespace ID of the attribute
required: true
- name: label
description: "Optional metadata 'labels' in the format: key=value"
shorthand: l
default: ""
default: ''
---
19 changes: 19 additions & 0 deletions docs/man/policy/attributes/unsafe/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Unsafe changes to attribute definitions
command:
name: unsafe
flags:
- name: force
description: Force unsafe change without confirmation
required: false
---

# Unsafe Changes to Attribute Definitions

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.
18 changes: 18 additions & 0 deletions docs/man/policy/attributes/unsafe/delete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Delete an attribute definition
command:
name: delete
flags:
- name: id
shorthand: i
description: ID of the attribute definition
required: true
---

# Unsafe Delete Warning

Deleting an Attribute Definition cascades deletion of any Attribute Values and any associated mappings underneath.

Any existing TDFs containing the deleted attribute of this name will be rendered inaccessible until it has been recreated.

Make sure you know what you are doing.
18 changes: 18 additions & 0 deletions docs/man/policy/attributes/unsafe/reactivate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Reactivate an attribute definition
command:
name: reactivate
flags:
- name: id
shorthand: i
description: ID of the attribute definition
required: true
---

# Unsafe Reactivate Warning

Reactivating an Attribute Definition can potentially open up an access path to any existing TDFs referencing values under that definition.

The Active/Inactive state of any Attribute Values under this Definition will NOT be changed.

Make sure you know what you are doing.
48 changes: 48 additions & 0 deletions docs/man/policy/attributes/unsafe/update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
title: Update an attribute definition
command:
name: update
flags:
- name: id
shorthand: i
description: ID of the attribute definition
required: true
- name: name
shorthand: n
description: Name of the attribute definition
- name: rule
shorthand: r
description: Rule of the attribute definition
enum:
- ANY_OF
- ALL_OF
- HIERARCHY
- name: values-order
shorthand: o
description: Order of the attribute values (IDs)
---

# Unsafe Update Warning

## Name Update

Renaming an Attribute Definition means any Values and any associated mappings underneath will now be tied to the new name.

Any existing TDFs containing attributes under the old definition name will be rendered inaccessible, and any TDFs tied to the new name
and already created may now become accessible.

## Rule Update

Altering a rule of an Attribute Definition changes the evaluation of entitlement to data. Existing TDFs of the same definition name
and values will now be accessible based on the updated rule. An `anyOf` rule becoming `hierarchy` or vice versa, for example, have
entirely different meanings and access evaluations.

## Values-Order Update

In the case of a `hierarchy` Attribute Definition Rule, the order of Values on the attribute has significant impact on data access.
Changing this order (complete, destructive replacement of the existing order) will impact access to data.

To remove Values from an Attribute Definition, delete them separately via the `values unsafe` commands. To add, utilize safe
`values create` commands.

Make sure you know what you are doing.
44 changes: 44 additions & 0 deletions pkg/handlers/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"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"
)

// TODO: Might be useful to map out the attribute rule definitions for help text in the CLI and TUI
Expand Down Expand Up @@ -100,6 +101,48 @@ func (h Handler) DeactivateAttribute(id string) (*policy.Attribute, error) {
return h.GetAttribute(id)
}

// Reactivates and returns reactivated attribute
func (h Handler) UnsafeReactivateAttribute(id string) (*policy.Attribute, error) {
_, err := h.sdk.Unsafe.UnsafeReactivateAttribute(h.ctx, &unsafe.UnsafeReactivateAttributeRequest{
Id: id,
})
if err != nil {
return nil, err
}
return h.GetAttribute(id)
}

// Deletes and returns error if deletion failed
func (h Handler) UnsafeDeleteAttribute(id, fqn string) error {
_, err := h.sdk.Unsafe.UnsafeDeleteAttribute(h.ctx, &unsafe.UnsafeDeleteAttributeRequest{
Id: id,
Fqn: fqn,
})
return err
}

// Deletes and returns error if deletion failed
func (h Handler) UnsafeUpdateAttribute(id, name, rule string, values_order []string) error {
req := &unsafe.UnsafeUpdateAttributeRequest{
Id: id,
Name: name,
}

if rule != "" {
r, err := GetAttributeRuleFromReadableString(rule)
if err != nil {
return fmt.Errorf("invalid attribute rule: %s", rule)
}
req.Rule = r
}
if len(values_order) > 0 {
req.ValuesOrder = values_order
}

_, err := h.sdk.Unsafe.UnsafeUpdateAttribute(h.ctx, req)
return err
}

func GetAttributeFqn(namespace string, name string) string {
return fmt.Sprintf("https://%s/attr/%s", namespace, name)
}
Expand All @@ -112,6 +155,7 @@ func GetAttributeRuleOptions() []string {
}
}

// Provides the un-prefixed human-readable attribute rule
func GetAttributeRuleFromAttributeType(rule policy.AttributeRuleTypeEnum) string {
switch rule {
case policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF:
Expand Down