Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add subject-mappings match to CLI #413

Merged
merged 13 commits into from
Nov 19, 2024
1 change: 1 addition & 0 deletions .github/spellcheck.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ SCS
SCSs
SDK
ShinyThing
SubjectConditionSets
TDF
TDF'd
TDFd
Expand Down
83 changes: 83 additions & 0 deletions cmd/policy-subjectMappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,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/opentdf/platform/protocol/go/policy/subjectmapping"
Expand Down Expand Up @@ -253,6 +254,70 @@ func policy_updateSubjectMapping(cmd *cobra.Command, args []string) {
HandleSuccess(cmd, id, t, updated)
}

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

subject := c.Flags.GetOptionalString("subject")
selectors = c.Flags.GetStringSlice("selector", selectors, cli.FlagsStringSliceOptions{Min: 0})

if len(selectors) > 0 && subject != "" {
cli.ExitWithError("Must provide either '--subject' or '--selector' flag values, not both", nil)
}

if subject != "" {
flattened, err := handlers.FlattenSubjectContext(subject)
if err != nil {
cli.ExitWithError("Could not process '--subject' value", err)
}
for _, item := range flattened {
selectors = append(selectors, item.Key)
}
}

matched, err := h.MatchSubjectMappings(selectors)
if err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to match subject mappings with selectors %v", selectors), err)
}

t := cli.NewTable(
cli.NewUUIDColumn(),
table.NewFlexColumn("subject_attrval_id", "Subject AttrVal: Id", cli.FlexColumnWidthFour),
table.NewFlexColumn("subject_attrval_value", "Subject AttrVal: Value", cli.FlexColumnWidthThree),
table.NewFlexColumn("actions", "Actions", cli.FlexColumnWidthTwo),
table.NewFlexColumn("subject_condition_set_id", "Subject Condition Set: Id", cli.FlexColumnWidthFour),
table.NewFlexColumn("subject_condition_set", "Subject Condition Set", cli.FlexColumnWidthThree),
)
rows := []table.Row{}
for _, sm := range matched {
var actionsJSON []byte
if actionsJSON, err = json.Marshal(sm.GetActions()); err != nil {
cli.ExitWithError("Error marshalling subject mapping actions", err)
}

var subjectSetsJSON []byte
if subjectSetsJSON, err = json.Marshal(sm.GetSubjectConditionSet().GetSubjectSets()); err != nil {
cli.ExitWithError("Error marshalling subject condition set", err)
}
metadata := cli.ConstructMetadata(sm.GetMetadata())

rows = append(rows, table.NewRow(table.RowData{
"id": sm.GetId(),
"subject_attrval_id": sm.GetAttributeValue().GetId(),
"subject_attrval_value": sm.GetAttributeValue().GetValue(),
"actions": string(actionsJSON),
"subject_condition_set_id": sm.GetSubjectConditionSet().GetId(),
"subject_condition_set": string(subjectSetsJSON),
"labels": metadata["Labels"],
"created_at": metadata["Created At"],
"updated_at": metadata["Updated At"],
}))
}
t = t.WithRows(rows)
HandleSuccess(cmd, "", t, matched)
}

func getSubjectMappingMappingActionEnumFromChoice(readable string) policy.Action_StandardAction {
switch readable {
case actionStandardDecrypt:
Expand Down Expand Up @@ -378,13 +443,31 @@ func init() {
deleteDoc.GetDocFlag("force").Description,
)

matchDoc := man.Docs.GetCommand("policy/subject-mappings/match",
man.WithRun(policy_matchSubjectMappings),
)
matchDoc.Flags().StringP(
matchDoc.GetDocFlag("subject").Name,
matchDoc.GetDocFlag("subject").Shorthand,
matchDoc.GetDocFlag("subject").Default,
matchDoc.GetDocFlag("subject").Description,
)
matchDoc.Flags().StringSliceVarP(
&selectors,
matchDoc.GetDocFlag("selector").Name,
matchDoc.GetDocFlag("selector").Shorthand,
[]string{},
matchDoc.GetDocFlag("selector").Description,
)

doc := man.Docs.GetCommand("policy/subject-mappings",
man.WithSubcommands(
createDoc,
getDoc,
listDoc,
updateDoc,
deleteDoc,
matchDoc,
),
)
policy_subjectMappingCmd := &doc.Command
Expand Down
2 changes: 1 addition & 1 deletion docs/man/interactive.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Interactive Mode
title: Interactive Mode (experimental)

command:
name: interactive
Expand Down
27 changes: 27 additions & 0 deletions docs/man/policy/subject-mappings/match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Match a subject or set of selectors to relevant subject mappings
command:
name: match
flags:
- name: subject
shorthand: s
description: A Subject Entity Representation string (JSON or JWT, auto-detected)
default: ''
- name: selector
shorthand: x
description: "Individual selectors (i.e. '.department' or '.realm_access.roles[]') that may be found in SubjectConditionSets"
---

Given that Subject Mappings contain Subject Condition Sets (see either relevant command for more information), this tool can consume an Entity Representation
or a set of provided selectors to query the platform Policy for any relevant Subject Mappings.

Given an Entity Representation of a Subject via `--subject` (an OIDC Access Token JWT, or a JSON object such as from an Entity Resolution Service response),
this command will parse all possible valid selectors and check those for presence in any Subject Condition Set referenced on a Subject Mapping to an Attribute Value.

Given a set of selectors (`--selector`), this command will look for any Subject Mappings with Subject Condition Sets containing those same selectors.

> [!NOTE]
> The values of the selectors and any `IN`/`NOT_IN`/`IN_CONTAINS` logic of Subject Condition Sets is irrelevant to this command.
> Evaluation of any matched conditions is handled by the Authorization Service to determine entitlements. This command
> is specifically for management of policy - to facilitate lookup of current conditions driven by known selectors as a
> precondition for administration of entitlement given the logical *operators* of the matched conditions and their relations.
jakedoublev marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ require (
github.com/evertras/bubble-table v0.16.1
github.com/gabriel-vasile/mimetype v1.4.5
github.com/go-jose/go-jose/v3 v3.0.3
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0
github.com/opentdf/platform/lib/flattening v0.1.1
github.com/opentdf/platform/protocol/go v0.2.20
github.com/opentdf/platform/sdk v0.3.18
github.com/spf13/cobra v1.8.1
Expand Down Expand Up @@ -55,6 +53,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gowebpki/jcs v1.0.1 // indirect
Expand Down Expand Up @@ -82,6 +81,7 @@ require (
github.com/muesli/termenv v0.15.2 // indirect
github.com/muhlemmer/gu v0.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opentdf/platform/lib/flattening v0.1.1 // indirect
github.com/opentdf/platform/lib/ocrypto v0.1.6 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,8 @@ github.com/opentdf/platform/lib/flattening v0.1.1 h1:la1f6PcRsc+yLH8+9UEr0ux6IRK
github.com/opentdf/platform/lib/flattening v0.1.1/go.mod h1:eyG7pe5UZlV+GI5/CymQD3xTAJxNhnP9M4QnBzaad1M=
github.com/opentdf/platform/lib/ocrypto v0.1.6 h1:rd4ctCZOE/c3qDJORtkSK9tw6dEXb+jbJXRRk4LcxII=
github.com/opentdf/platform/lib/ocrypto v0.1.6/go.mod h1:ne+l8Q922OdzA0xesK3XJmfECBnn5vLSGYU3/3OhiHM=
github.com/opentdf/platform/protocol/go v0.2.18 h1:s+TVZkOPGCzy7WyObtJWJNaFeOGDUTuSmAsq3omvugY=
github.com/opentdf/platform/protocol/go v0.2.18/go.mod h1:WqDcnFQJb0v8ivRQPidbehcL8ils5ZSZYXkuv0nyvsI=
github.com/opentdf/platform/protocol/go v0.2.20 h1:FPU1ZcXvPm/QeE2nqgbD/HMTOCICQSD0DoncQbAZ1ws=
github.com/opentdf/platform/protocol/go v0.2.20/go.mod h1:TWIuf387VeR3q0TL4nAMKQTWEqqID+8Yjao76EX9Dto=
github.com/opentdf/platform/sdk v0.3.17 h1:Uo/kTMneB18i0gZNfTRtvw34bGLFUc8BEnA/BMK0VVs=
github.com/opentdf/platform/sdk v0.3.17/go.mod h1:c2+nrsRLvLf2OOryXnNy0iGZN/TScc21Pul7uqKVXIs=
github.com/opentdf/platform/sdk v0.3.18 h1:IY6fNrOfQD9lF/hZp9ewZsH0PMuLe17HlSE1A5kyIWc=
github.com/opentdf/platform/sdk v0.3.18/go.mod h1:u+XZhVRsMq5blukCFCHcjk6HLCp4Y5mmIQu7GhtKQ3E=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
Expand Down
13 changes: 13 additions & 0 deletions pkg/handlers/subjectmappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ func (h Handler) DeleteSubjectMapping(id string) (*policy.SubjectMapping, error)
return resp.GetSubjectMapping(), err
}

func (h Handler) MatchSubjectMappings(selectors []string) ([]*policy.SubjectMapping, error) {
subjectProperties := make([]*policy.SubjectProperty, len(selectors))
for i, selector := range selectors {
subjectProperties[i] = &policy.SubjectProperty{
ExternalSelectorValue: selector,
}
}
resp, err := h.sdk.SubjectMapping.MatchSubjectMappings(h.ctx, &subjectmapping.MatchSubjectMappingsRequest{
SubjectProperties: subjectProperties,
})
return resp.GetSubjectMappings(), err
}

func GetSubjectMappingOperatorFromChoice(readable string) policy.SubjectMappingOperatorEnum {
switch readable {
case SubjectMappingOperatorIn:
Expand Down
Loading