platform
platform copied to clipboard
ADR: obligations implementation path
Table of Contents
- Background
- Option 1: Obligations as a separate policy construct
- Option 2: Obligations via flag within existing attributes as previously detailed
- Level of Effort
- Pros/Cons
Background
Relevant ADR: https://github.com/opentdf/platform/issues/874 Relevant implementation issue: https://github.com/opentdf/platform/issues/1260
Example PEP obligations once Subject entity found to be entitled in a data access decision:
- watermark
- change-all-images-to-ducks
- make-font-size-20
Option 1: Obligations as a separate policy construct
- separate FQN for obligations
https://<namespace>/oblg/<name>
- no parent/child relationship among obligations (they're flat and distinct from one another) but nothing stops you from naming it
https://namespace/oblg/drm:watermark
- no rule like attributes, or implicit
anyOf
rule like we've discussed
erDiagram
obligations {
uuid id
uuid namespace_id FK
string name
jsonb feature_context "demonstration: we could add robust feature context to an obligation like an expiration timestamp in whatever new columns"
jsonb metadata "conventional policy metadata labels key/value store"
compIdx comp_key UK "namespace_id + name"
}
obligation_assignments {
uuid id
uuid obligation_id
uuid attribute_value_id
}
obligation_fulfillments {
uuid id
uuid obligation_id
enum entity_scope "subject | environment"
jsonb fulfillment_conditions "condition sets not tied to subjects"
jsonb metadata "conventional policy metadata labels key/value store"
}
attribute_values {
uuid id PK "abbreviated table"
uuid attribute_definition_id FK
varchar value
}
namespaces {
uuid id PK
varchar name UK
}
attribute_definitions {
uuid id PK "abbreviated table"
uuid namespace_id FK
varchar name
enum rule
}
namespaces ||--|{ attribute_definitions : has
namespaces ||--|{ obligations : has
attribute_definitions ||--|{ attribute_values : has
obligations ||--o{ obligation_assignments : links
attribute_values ||--o{ obligation_assignments : links
obligations ||--o{ obligation_fulfillments : "fulfilled by"
%% abbreviations
subject_mappings ||--o{ attribute_values : entitles
subject_condition_sets ||--o{ subject_mappings : relates
kas_val_grants ||--o{ attribute_values : grants
kas_attr_grants ||--o{ attribute_definitions : grants
Policy work:
- new
obligations
table - new
obligation_assignments
table linking derived obligations and attribute_values - new
obligation_fulfillments
table - obligation fulfillment conditions protos (not purely subject-oriented, so can't reuse SCS from entitlements/SubjectMappings without breaking changes)
- CRUD RPCs and db functions for:
- CRUD obligations
- assign/unassign obligations to attribute values
- CRUD obligation_fulfillments for obligations
- tests for db CRUD of obligations, assignments, fulfillments
- docs for CRUD of obligations, assignments, fulfillments
- FQN lookup for obligation values (reuses existing FQNs table?)
https://namespace/oblg/<name>, i.e. https://demo.com/oblg/drm:watermark or demo.com/oblg/readonly
- obligations RPCs/service rolled up into policy with dedicated casbin policy
- new CLI CRUD support for obligations:
- CRUD obligations
- assign/unassign obligations to attribute values
- CRUD obligation_fulfillments for obligations
- Value protos are updated to include derived obligations
- GetAttributesByValueFQNs adds support in protos/db to return derived obligations for values
- GetObligationsByFQNs (new RPC) expects obligation FQN structure and returns obligation fulfillment conditions
Authz/KAS/SDK work:
- Read derived obligations from resource attributes
- Contextualize obligations on TDF data policy
- Logic/tests to resolve obligation condition logic
- SDK must allow obligation-FQN-structure strings alongside data attribute FQN strings
- TODO: AccessPDP resolves obligation fulfillments as scoped (multiple obligations/multiple entities = what behavior?)
- GetDecisions calls AccessPDP correctly and returns obligations correctly
- KAS returns obligations along with /rewrap
- JS SDK support for obligations in response?
- Go SDK support for obligations in response?
Option 2: Obligations via flag within existing attributes as previously detailed
erDiagram
obligation_assignments {
uuid id
uuid obligation_attribute_value_id
uuid data_attribute_value_id
}
obligation_fulfillments {
uuid id
uuid obligation_attribute_value_id
enum entity_scope "subject | environment"
jsonb fulfillment_conditions "condition sets not tied to subjects"
jsonb metadata "conventional policy metadata labels key/value store"
}
attribute_values {
uuid id PK
uuid attribute_definition_id FK
varchar value
jsonb metadata
compIdx comp_key UK "ns_id + ad_id + value"
}
namespaces {
uuid id PK
varchar name UK
}
attribute_definitions {
uuid id PK
uuid namespace_id FK
varchar name
enum type "data | obligation"
enum rule "enforced by policy services to be anyOf if obligation type"
jsonb metadata
compIdx comp_key UK "ns_id + name"
}
namespaces ||--|{ attribute_definitions : has
attribute_definitions ||--|{ attribute_values : has
attribute_values ||--o{ obligation_assignments : "data attr val -> 1+ obligations"
obligation_assignments ||--o{ attribute_values : "obligation -> 1+ data attr vals"
attribute_values ||--o{ obligation_fulfillments : "IFF obligation, fulfills"
%% abbreviations
subject_mappings ||--o{ attribute_values : "IFF data attr def val, entitles"
subject_condition_sets ||--o{ subject_mappings : relates
kas_val_grants ||--o{ attribute_values : "IFF data attr def val, grants"
kas_attr_grants ||--o{ attribute_definitions : "IFF data attr def val, grants"
Policy work
-
policy database migration to:
- add type
ENUM('data','obligation')
- write all current platform attributes to the 'data' type
- add type
-
new
obligation_assignments
table linking derived obligation_attribute_values and data_attribute_values -
new
obligation_fulfillments
table -
Update attributes CRUD to support obligations via type enum flag
- Create
- attributes are implicitly defaulted 'data' type unless defined as 'obligation'
- accept type on Create in attr definition protos/rpcs
- implicitly default anyOf rule only on 'obligation' type Create, or decide to enforce 'anyOf' and fail?
- Update proto comments about two types of attributes and what they mean
- tests + docs
- GET/LIST
- Update Value to query derived obligations from DB and set in response IFF 'data' attribute
- Update Value proto to provide obligation_fulfillments IFF 'obligation' attribute
- Update Definition proto to provide obligation type in response
- tests + docs
- UNSAFE
- enforce cannot change rule on an obligation-type attribute (unless we want to support rules in obligations other than
anyOf
?) - tests + docs
- enforce cannot change rule on an obligation-type attribute (unless we want to support rules in obligations other than
- Create
-
Update other policy CRUD to differentiate between attribute types
- subject mappings
- Create must fail if mapped to an obligation-type attribute value
- Update must fail if mapped to an obligation-type attribute value
- tests + docs
- resource mappings
- Create must fail if mapped to an obligation-type attribute value
- Update must fail if mapped to an obligation-type attribute value
- tests + docs
- KAS grants to attribute definitions
- Create must fail if mapped to an obligation-type attribute value
- Update must fail if mapped to an obligation-type attribute value
- tests + docs
- KAS grants to attribute values
- Create must fail if mapped to an obligation-type attribute value
- Update must fail if mapped to an obligation-type attribute value
- tests + docs
- subject mappings
-
obligation fulfillment conditions protos (not purely subject-oriented, so can't reuse subject condition sets without breaking changes)
-
FQN lookup for obligation values (no change)
https://namespace/attr/obligation_name/value/<value>, i.e. https://demo.com/attr/drm/value/watermark
-
CLI CRUD for obligations (still separate subcommands to improve documentation specificity and clarity)
- CRUD obligations via policy attribute RPCs under the hood
- assign/unassign obligations to attribute values
- CRUD obligation_fulfillments to obligation_values
-
Hide obligation type attributes/values from existing CLI CRUD of those policy objects
-
docs/tests for new CRUD behaviors (assignments, fulfillments)
-
Value protos are updated to include derived obligations
-
GetAttributesByValueFQNs adds support in protos/db to return derived obligations for values
-
GetAttributesByValueFQNs is updated to have a different response if the requested FQN was an obligation-type attribute value
Authz/KAS/SDK work:
- Read derived obligations from resource attributes
- Contextualize obligations on TDF data policy
- Logic/tests to resolve obligation condition logic
- TODO: AccessPDP resolves fulfillment conditions by scope (multiple obligations/multiple entities = what behavior?)
- GetDecisions calls AccessPDP correctly and returns obligations correctly
- KAS returns obligations along with /rewrap
- JS SDK support for obligations in response?
- Go SDK support for obligations in response?
Level of Effort (LOE) cost comparison to implement, maintain, understand, and develop
Path 1: Distinctly Separate Obligation Constructs | Work | LOE |
---|---|---|
Initially implement | M/L | |
Maintain as obligations and attributes each evolve | S/M | |
Understand for admins | S/M | |
Understand as developers building on top | S/M |
Path 2: Obligations as Attribute Types | Work | LOE |
---|---|---|
Initially implement | M/L | |
Maintain as obligations evolve (unknown but coupled with shared concerns) | M/L | |
Understand for admins | M | |
Understand as developers building on top | M |
Other data points
-
Adding the
unsafe
policy service was 9 new RPC endpoints, db functionalities with tests, 9 new CLI paths, and docs and took one engineer (myself) ~2 weeks with around 2/3 focus -
We don't know exactly how obligations will evolve/mature over time (DLP, malware scanning, watermark)
-
Making obligations attributes means they are parent/child, rule-associated (implicitly), with an FQN that is ambiguous as to attribute type
-
The two options mostly overlap in cost to implement beyond policy, with the following roughly the same:
- KAS/Authz/SDK/AccessPDP support for obligations work once read from policy services
- CLI support for obligations work
Pros/Cons of Options
Option 1: Obligations as a separate construct
- :green_square: Good, because long-term costs are expected to be lower (maintainance, testing, onboarding)
- :green_square: Good, because more flexibility evolving obligations apart from attributes as our understanding of use cases matures
- :green_square: Good, because less implicit behavior (requiring
anyOf
rule and parent/child structure) - :green_square: Good, because we can unblock differentiated RBAC casbin auth on obligations distinct from attributes
- :green_square: Good, because it's less burden on admins to understand, and easier to understand when totally separate from attributes (i.e. "here is what an attribute means, and here is what an obligation means")
- :green_square: Good, because smaller blast radius for bugs and breaking changes
- :green_square: Good, because nothing stopping us from eventually deriving obligations off entities with separate constructs used while deriving - they're decoupled and flexible
- :green_square: Good, because easier to add additional context to obligations like time frames or other conditions driving feature behavior post-obligation
- :green_square: Good, because meets product use case requirements of "data-driven and derivable obligations satisfied by varied entity types under conditions within platform policy"
- :yellow_square: Neutral, because PEP and non-policy costs are close to the same
- :yellow_square: Neutral, because short-term policy implementation costs are close to the same, but maybe a little smaller
- :yellow_square: Neutral, because if we need
hierarchy
orallOf
rules on obligations, we'd need a new enum construct, but it would be decoupled so they could evolve separately - :red_square: Bad, because further complicates the policy relational db schema (hard to hold in your head at once)
Option 2: Obligations via flag within existing attributes as previously detailed
- :green_square: Good, because meets product use case requirements of "data-driven and derivable obligations satisfied by varied entity types under conditions within platform policy"
- :yellow_square: Neutral, because PEP and non-policy costs are close to the same
- :yellow_square: Neutral, because we may still be able to do things like derive obligations from entities, but it is going to be harder to understand as a developer if obligations are attributes
- :yellow_square: Neutral, because short-term policy implementation costs are close to the same, but maybe a little bigger
- :yellow_square: Neutral, because fewer tables is good, but coupling to attributes is limiting
- :red_square: Bad, because larger blast radius within policy for bugs and breaking changes given all the new IF/Else checks and behaviors
- :red_square: Bad, because less flexibility evolving obligations apart from attributes as our understanding of use cases matures
- :red_square: Bad, because implicit behavior around
anyOf
rule - :red_square: Bad, because long-term costs are lower (maintainance, testing, onboarding)
- :red_square: Bad, because it's a greater burden on admins to understand (i.e. "here is what an attribute means, unless it's an obligation, and then it really means something different" and vice versa)