Skip to main content

Managing Policy

Policy in OpenTDF is the set of rules that govern who can access data and under what conditions. It is made up of namespaces, attributes, subject mappings, and subject condition sets. The SDK provides CRUD access to these policy rules through remote gRPC calls powered by the platform service client.


Namespaces

A namespace is the top-level organizational unit for attributes. Attribute FQNs are scoped to a namespace URI (e.g., https://example.com).

Soft-delete only

Namespaces are deactivated, not permanently deleted. There is no hard-delete operation and no restore operation exposed via the SDK.

Creating a Namespace
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/namespaces"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// Create Namespace
namespace := &namespaces.CreateNamespaceRequest{
Name: "opentdf.io",
}

_, err = client.Namespaces.CreateNamespace(context.Background(), namespace)
if err != nil {
log.Fatal(err)
}
}
List Namespaces
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/namespaces"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// List All Namespaces
namespaces, err := client.Namespaces.ListNamespaces(context.Background(), &namespaces.ListNamespacesRequest{})
if err != nil {
log.Fatal(err)
}
for _, namespace := range namespaces.GetNamespaces() {
log.Printf("Namespace: %s\n", namespace.GetName())
}
}
Get a Namespace
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/namespaces"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

// Look up by UUID
resp, err := client.Namespaces.GetNamespace(context.Background(),
&namespaces.GetNamespaceRequest{
Identifier: &namespaces.GetNamespaceRequest_NamespaceId{
NamespaceId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
},
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Namespace: %s (ID: %s)\n", resp.GetNamespace().GetName(), resp.GetNamespace().GetId())

// Alternatively, look up by FQN
respByFqn, err := client.Namespaces.GetNamespace(context.Background(),
&namespaces.GetNamespaceRequest{
Identifier: &namespaces.GetNamespaceRequest_Fqn{
Fqn: "https://example.com",
},
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Namespace by FQN: %s\n", respByFqn.GetNamespace().GetName())
}
Update a Namespace
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/common"
"github.com/opentdf/platform/protocol/go/policy/namespaces"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Namespaces.UpdateNamespace(context.Background(),
&namespaces.UpdateNamespaceRequest{
Id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
Metadata: &common.MetadataMutable{
Labels: map[string]string{
"owner": "platform-team",
"env": "production",
},
},
MetadataUpdateBehavior: common.MetadataUpdateEnum_METADATA_UPDATE_ENUM_REPLACE,
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated namespace ID: %s\n", resp.GetNamespace().GetId())
}

MetadataUpdateEnum_METADATA_UPDATE_ENUM_REPLACE overwrites all existing labels. Use METADATA_UPDATE_ENUM_EXTEND to merge with existing labels.

Deactivate a Namespace
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/namespaces"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

_, err = client.Namespaces.DeactivateNamespace(context.Background(),
&namespaces.DeactivateNamespaceRequest{
Id: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
},
)
if err != nil {
log.Fatal(err)
}
log.Println("Namespace deactivated.")
}
Listing inactive namespaces

By default, ListNamespaces returns only active namespaces. To include inactive ones, set State to ACTIVE_STATE_ENUM_INACTIVE or ACTIVE_STATE_ENUM_ANY on the request.


Attributes

An attribute defines a classification dimension (e.g., classification, department). Each attribute belongs to a namespace, has a rule (ALL_OF, ANY_OF, or HIERARCHY), and contains one or more values.

Soft-delete only

Attributes and attribute values are deactivated, not permanently deleted.

Create Attribute
package main

import (
"context"
"crypto/rand"
"log"

"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/protocol/go/policy/namespaces"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// List namespaces to get a namespace ID
listResponse, err := client.Namespaces.ListNamespaces(context.Background(), &namespaces.ListNamespacesRequest{})
if err != nil {
log.Fatalf("failed to list namespaces: %s", err)
}

if len(listResponse.GetNamespaces()) == 0 {
log.Fatal("no namespaces found")
}

namespaceID := listResponse.GetNamespaces()[0].GetId()

// Create a new attribute
attrRequest := &attributes.CreateAttributeRequest{
NamespaceId: namespaceID,
Name: "role" + "-" + rand.Text()[:4],
Rule: policy.AttributeRuleTypeEnum_ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF,
Values: []string{"admin", "developer", "guest"},
}

attribute, err := client.Attributes.CreateAttribute(context.Background(), attrRequest)
if err != nil {
log.Fatal(err)
}
log.Printf("Created attribute: %s with ID: %s in namespace: %s\n", attribute.GetAttribute().Name, attribute.GetAttribute().GetId(), namespaceID)
}
List Attributes
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// List attributes

attrs, err := client.Attributes.ListAttributes(context.Background(), &attributes.ListAttributesRequest{})
if err != nil {
log.Fatal(err)
}

for _, attr := range attrs.GetAttributes() {
log.Printf("Attribute: %s, ID: %s, ", attr.GetName(), attr.GetId())
for _, value := range attr.Values {
log.Printf("Value: %s, ID: %s", value.GetValue(), value.GetId())
}
}
}
Get an Attribute
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

// Look up by FQN
resp, err := client.Attributes.GetAttribute(context.Background(),
&attributes.GetAttributeRequest{
Identifier: &attributes.GetAttributeRequest_Fqn{
Fqn: "https://example.com/attr/classification",
},
},
)
if err != nil {
log.Fatal(err)
}
attr := resp.GetAttribute()
log.Printf("Attribute: %s, Rule: %s\n", attr.GetName(), attr.GetRule())
for _, v := range attr.GetValues() {
log.Printf(" Value: %s (ID: %s)\n", v.GetValue(), v.GetId())
}
}
Update an Attribute
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/common"
"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.UpdateAttribute(context.Background(),
&attributes.UpdateAttributeRequest{
Id: "a1b2c3d4-0000-0000-0000-000000000001",
Metadata: &common.MetadataMutable{
Labels: map[string]string{"reviewed": "true"},
},
MetadataUpdateBehavior: common.MetadataUpdateEnum_METADATA_UPDATE_ENUM_EXTEND,
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated attribute ID: %s\n", resp.GetAttribute().GetId())
}
Deactivate an Attribute
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.DeactivateAttribute(context.Background(),
&attributes.DeactivateAttributeRequest{
Id: "a1b2c3d4-0000-0000-0000-000000000001",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deactivated attribute: %s\n", resp.GetAttribute().GetId())
}

Attribute Values

Attribute values are the individual members of an attribute (e.g., secret, top-secret under classification). They can be managed independently after the parent attribute is created.

Create an Attribute Value
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.CreateAttributeValue(context.Background(),
&attributes.CreateAttributeValueRequest{
AttributeId: "a1b2c3d4-0000-0000-0000-000000000001",
Value: "top-secret",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Created value: %s (ID: %s)\n", resp.GetValue().GetValue(), resp.GetValue().GetId())
}
List Attribute Values
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.ListAttributeValues(context.Background(),
&attributes.ListAttributeValuesRequest{
AttributeId: "a1b2c3d4-0000-0000-0000-000000000001",
},
)
if err != nil {
log.Fatal(err)
}
for _, v := range resp.GetValues() {
log.Printf("Value: %s (ID: %s)\n", v.GetValue(), v.GetId())
}
}
Get an Attribute Value
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

// Look up by FQN
resp, err := client.Attributes.GetAttributeValue(context.Background(),
&attributes.GetAttributeValueRequest{
Identifier: &attributes.GetAttributeValueRequest_Fqn{
Fqn: "https://example.com/attr/classification/value/secret",
},
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Value: %s (FQN: %s)\n", resp.GetValue().GetValue(), resp.GetValue().GetFqn())
}
Get Attribute Values by FQNs

Batch-fetch multiple attribute values by their FQNs in a single request. Returns a map keyed by FQN, each entry containing both the parent attribute definition and the specific value.

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.GetAttributeValuesByFqns(context.Background(),
&attributes.GetAttributeValuesByFqnsRequest{
Fqns: []string{
"https://example.com/attr/classification/value/secret",
"https://example.com/attr/department/value/engineering",
},
},
)
if err != nil {
log.Fatal(err)
}
for fqn, entry := range resp.GetFqnAttributeValues() {
log.Printf("FQN: %s -> Attribute: %s, Value: %s\n",
fqn,
entry.GetAttribute().GetName(),
entry.GetValue().GetValue(),
)
}
}
Update an Attribute Value
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/common"
"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.UpdateAttributeValue(context.Background(),
&attributes.UpdateAttributeValueRequest{
Id: "v1b2c3d4-0000-0000-0000-000000000001",
Metadata: &common.MetadataMutable{
Labels: map[string]string{"deprecated": "false"},
},
MetadataUpdateBehavior: common.MetadataUpdateEnum_METADATA_UPDATE_ENUM_EXTEND,
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated value ID: %s\n", resp.GetValue().GetId())
}
Deactivate an Attribute Value
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/attributes"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Attributes.DeactivateAttributeValue(context.Background(),
&attributes.DeactivateAttributeValueRequest{
Id: "v1b2c3d4-0000-0000-0000-000000000001",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deactivated value: %s\n", resp.GetValue().GetId())
}

Subject Condition Sets

A subject condition set (SCS) is a reusable, named set of conditions that evaluate claims from an entity's token. Multiple subject mappings can reference the same SCS.

Hard-delete

Subject condition sets are permanently deleted (not deactivated).

Create Subject Condition Set
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// Create Subject Condition Set

conditionset := &subjectmapping.CreateSubjectConditionSetRequest{
SubjectConditionSet: &subjectmapping.SubjectConditionSetCreate{
SubjectSets: []*policy.SubjectSet{
{
ConditionGroups: []*policy.ConditionGroup{
{
BooleanOperator: policy.ConditionBooleanTypeEnum_CONDITION_BOOLEAN_TYPE_ENUM_AND,
Conditions: []*policy.Condition{
{
SubjectExternalSelectorValue: ".clientId",
Operator: policy.SubjectMappingOperatorEnum_SUBJECT_MAPPING_OPERATOR_ENUM_IN,
SubjectExternalValues: []string{"opentdf"},
},
},
},
},
},
},
},
}

resp, err := client.SubjectMapping.CreateSubjectConditionSet(context.Background(), conditionset)
if err != nil {
log.Fatal(err)
}

log.Printf("Created Subject Condition Set with ID: %s\n", resp.GetSubjectConditionSet().GetId())
}
List Subject Condition Sets
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.ListSubjectConditionSets(context.Background(),
&subjectmapping.ListSubjectConditionSetsRequest{},
)
if err != nil {
log.Fatal(err)
}
for _, scs := range resp.GetSubjectConditionSets() {
log.Printf("SCS ID: %s\n", scs.GetId())
}
}
Get a Subject Condition Set

The response also includes AssociatedSubjectMappings — all subject mappings currently referencing this condition set.

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.GetSubjectConditionSet(context.Background(),
&subjectmapping.GetSubjectConditionSetRequest{
Id: "a0b1c2d3-0000-0000-0000-000000000099",
},
)
if err != nil {
log.Fatal(err)
}
scs := resp.GetSubjectConditionSet()
log.Printf("SCS ID: %s\n", scs.GetId())
log.Printf("Associated subject mappings: %d\n", len(resp.GetAssociatedSubjectMappings()))
}
Update a Subject Condition Set

Providing SubjectSets replaces the entire condition tree. Omit it to update metadata only.

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.UpdateSubjectConditionSet(context.Background(),
&subjectmapping.UpdateSubjectConditionSetRequest{
Id: "a0b1c2d3-0000-0000-0000-000000000099",
SubjectSets: []*policy.SubjectSet{
{
ConditionGroups: []*policy.ConditionGroup{
{
BooleanOperator: policy.ConditionBooleanTypeEnum_CONDITION_BOOLEAN_TYPE_ENUM_AND,
Conditions: []*policy.Condition{
{
SubjectExternalSelectorValue: ".clientId",
Operator: policy.SubjectMappingOperatorEnum_SUBJECT_MAPPING_OPERATOR_ENUM_IN,
SubjectExternalValues: []string{"my-service", "my-other-service"},
},
},
},
},
},
},
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated SCS ID: %s\n", resp.GetSubjectConditionSet().GetId())
}
Delete a Subject Condition Set
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.DeleteSubjectConditionSet(context.Background(),
&subjectmapping.DeleteSubjectConditionSetRequest{
Id: "a0b1c2d3-0000-0000-0000-000000000099",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deleted SCS ID: %s\n", resp.GetSubjectConditionSet().GetId())
}
Delete All Unmapped Subject Condition Sets

Permanently deletes all subject condition sets that are not referenced by any subject mapping. Use this as a maintenance operation to clean up orphaned condition sets.

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.DeleteAllUnmappedSubjectConditionSets(context.Background(),
&subjectmapping.DeleteAllUnmappedSubjectConditionSetsRequest{},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deleted %d unmapped subject condition sets.\n", len(resp.GetSubjectConditionSets()))
}

Subject Mappings

A subject mapping links a subject condition set to a specific attribute value and a set of permitted actions (e.g., DECRYPT). It answers the question: "which entities are allowed to do what with data carrying this attribute value?"

Hard-delete

Subject mappings are permanently deleted (not deactivated). There is no restore operation.

Create Subject Mapping
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// Create Subject Mapping
subjectMapping := &subjectmapping.CreateSubjectMappingRequest{
AttributeValueId: "224c9d29-2cd4-4a38-b6ad-5f025ca93a8c",
Actions: []*policy.Action{
{
Value: &policy.Action_Standard{
Standard: policy.Action_STANDARD_ACTION_DECRYPT,
},
},
},
ExistingSubjectConditionSetId: "890b26db-4ee4-447f-ae8a-2862d922eeef",
}

_, err = client.SubjectMapping.CreateSubjectMapping(context.Background(), subjectMapping)
if err != nil {
log.Fatal(err)
}
}
List Subject Mappings
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {

platformEndpoint := "http://localhost:8080"

// Create a new client
client, err := sdk.New(
platformEndpoint,
sdk.WithClientCredentials("opentdf", "secret", nil),
)

if err != nil {
log.Fatal(err)
}

// List Subject Mapping

subjectmappings, err := client.SubjectMapping.ListSubjectMappings(context.Background(), &subjectmapping.ListSubjectMappingsRequest{})
if err != nil {
log.Fatal(err)
}

for _, sm := range subjectmappings.GetSubjectMappings() {
log.Printf("Subject Mapping: %s", sm.GetId())
}
}

Get a Subject Mapping
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.GetSubjectMapping(context.Background(),
&subjectmapping.GetSubjectMappingRequest{
Id: "890b26db-4ee4-447f-ae8a-2862d922eeef",
},
)
if err != nil {
log.Fatal(err)
}
sm := resp.GetSubjectMapping()
log.Printf("Subject Mapping ID: %s, Attribute Value ID: %s\n",
sm.GetId(), sm.GetAttributeValue().GetId())
}
Update a Subject Mapping
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

// Replace the actions list on an existing subject mapping
resp, err := client.SubjectMapping.UpdateSubjectMapping(context.Background(),
&subjectmapping.UpdateSubjectMappingRequest{
Id: "890b26db-4ee4-447f-ae8a-2862d922eeef",
Actions: []*policy.Action{
{
Value: &policy.Action_Standard{
Standard: policy.Action_STANDARD_ACTION_DECRYPT,
},
},
},
// Optionally replace the subject condition set:
// SubjectConditionSetId: "new-scs-uuid",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated subject mapping ID: %s\n", resp.GetSubjectMapping().GetId())
}

Providing Actions replaces the entire actions list. Omit it to leave actions unchanged. Providing SubjectConditionSetId swaps the linked condition set.

Delete a Subject Mapping
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.DeleteSubjectMapping(context.Background(),
&subjectmapping.DeleteSubjectMappingRequest{
Id: "890b26db-4ee4-447f-ae8a-2862d922eeef",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deleted subject mapping ID: %s\n", resp.GetSubjectMapping().GetId())
}
Match Subject Mappings

Given a list of subject properties (key-value pairs from an entity's claims or attributes), return all subject mappings whose condition sets match. This is used to determine which access grants apply to a given entity.

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/subjectmapping"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.SubjectMapping.MatchSubjectMappings(context.Background(),
&subjectmapping.MatchSubjectMappingsRequest{
SubjectProperties: []*policy.SubjectProperty{
{ExternalSelectorValue: ".clientId", ExternalValue: "my-service"},
{ExternalSelectorValue: ".realm_access.roles", ExternalValue: "developer"},
},
},
)
if err != nil {
log.Fatal(err)
}
for _, sm := range resp.GetSubjectMappings() {
log.Printf("Matched subject mapping ID: %s\n", sm.GetId())
}
}

Obligations

An obligation is a PDP-to-PEP directive that accompanies an access decision: "permit, provided these controls are enforced." Obligations are scoped to a namespace and can carry multiple values and triggers. See Obligations for the policy concept.

Access to the Obligations service:

client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("client-id", "client-secret", nil),
)
if err != nil {
log.Fatal(err)
}

// sdk.Obligations provides the full ObligationsServiceClient interface.

Obligation Definitions

List Obligations
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/obligations"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Obligations.ListObligations(context.Background(),
&obligations.ListObligationsRequest{},
)
if err != nil {
log.Fatal(err)
}
for _, obl := range resp.GetObligations() {
log.Printf("Obligation: %s FQN: %s\n", obl.GetName(), obl.GetFqn())
}
}

To filter by namespace, set NamespaceId or NamespaceFqn on the request:

resp, err := client.Obligations.ListObligations(context.Background(),
&obligations.ListObligationsRequest{
NamespaceFqn: "https://example.com",
},
)
Get an Obligation

Look up by ID or FQN (one is required):

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/obligations"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Obligations.GetObligation(context.Background(),
&obligations.GetObligationRequest{
Fqn: "https://example.com/obl/drm",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Obligation ID: %s\n", resp.GetObligation().GetId())
}
Get Obligations by FQNs

Batch-fetch multiple obligations by FQN. Returns a map of FQN to Obligation:

resp, err := client.Obligations.GetObligationsByFQNs(context.Background(),
&obligations.GetObligationsByFQNsRequest{
Fqns: []string{
"https://example.com/obl/drm",
"https://example.com/obl/audit",
},
},
)
if err != nil {
log.Fatal(err)
}
for fqn, obl := range resp.GetFqnObligationMap() {
log.Printf("%s → %s\n", fqn, obl.GetId())
}
Create an Obligation

Provide either a NamespaceId or NamespaceFqn (one is required), plus a Name. Values can be created inline or added later via Create Obligation Value:

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/obligations"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Obligations.CreateObligation(context.Background(),
&obligations.CreateObligationRequest{
NamespaceFqn: "https://example.com",
Name: "drm",
Values: []string{"watermarking", "no-download"},
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Created obligation: %s FQN: %s\n",
resp.GetObligation().GetId(), resp.GetObligation().GetFqn())
}

The resulting FQN follows the convention <namespace>/obl/<name> (e.g. https://example.com/obl/drm).

Update an Obligation

Only the fields you set are updated. Provide the obligation Id:

resp, err := client.Obligations.UpdateObligation(context.Background(),
&obligations.UpdateObligationRequest{
Id: "3f4a7c12-...",
Name: "digital-rights",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated obligation FQN: %s\n", resp.GetObligation().GetFqn())
Delete an Obligation

Provide either Id or Fqn:

resp, err := client.Obligations.DeleteObligation(context.Background(),
&obligations.DeleteObligationRequest{
Fqn: "https://example.com/obl/drm",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deleted obligation: %s\n", resp.GetObligation().GetId())

Obligation Values

Each obligation can carry one or more values. Value FQNs follow the convention <namespace>/obl/<obligation_name>/value/<value> (e.g. https://example.com/obl/drm/value/watermarking).

Get an Obligation Value
resp, err := client.Obligations.GetObligationValue(context.Background(),
&obligations.GetObligationValueRequest{
Fqn: "https://example.com/obl/drm/value/watermarking",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Value ID: %s\n", resp.GetValue().GetId())
Get Obligation Values by FQNs
resp, err := client.Obligations.GetObligationValuesByFQNs(context.Background(),
&obligations.GetObligationValuesByFQNsRequest{
Fqns: []string{
"https://example.com/obl/drm/value/watermarking",
"https://example.com/obl/drm/value/no-download",
},
},
)
if err != nil {
log.Fatal(err)
}
for fqn, val := range resp.GetFqnValueMap() {
log.Printf("%s → %s\n", fqn, val.GetId())
}
Create an Obligation Value

Provide either ObligationId or ObligationFqn, plus the Value string:

package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/policy/obligations"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Obligations.CreateObligationValue(context.Background(),
&obligations.CreateObligationValueRequest{
ObligationFqn: "https://example.com/obl/drm",
Value: "encrypt-at-rest",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Created value: %s FQN: %s\n",
resp.GetValue().GetId(), resp.GetValue().GetFqn())
}
Update an Obligation Value

Provide the value Id. Triggers provided here replace all existing triggers for the value:

resp, err := client.Obligations.UpdateObligationValue(context.Background(),
&obligations.UpdateObligationValueRequest{
Id: "9a1b2c3d-...",
Value: "encrypt-at-rest-v2",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Updated value FQN: %s\n", resp.GetValue().GetFqn())
Delete an Obligation Value
resp, err := client.Obligations.DeleteObligationValue(context.Background(),
&obligations.DeleteObligationValueRequest{
Fqn: "https://example.com/obl/drm/value/watermarking",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Deleted value: %s\n", resp.GetValue().GetId())

Triggers

A trigger links an obligation value to a specific action + attribute value combination (and optionally a PEP identifier). When that action is performed on data carrying that attribute value, the obligation fires.

Add an Obligation Trigger
package main

import (
"context"
"log"

"github.com/opentdf/platform/protocol/go/common"
"github.com/opentdf/platform/protocol/go/policy/obligations"
"github.com/opentdf/platform/sdk"
)

func main() {
client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

resp, err := client.Obligations.AddObligationTrigger(context.Background(),
&obligations.AddObligationTriggerRequest{
ObligationValue: &common.IdFqnIdentifier{
Fqn: "https://example.com/obl/drm/value/watermarking",
},
Action: &common.IdNameIdentifier{
Name: "DECRYPT",
},
AttributeValue: &common.IdFqnIdentifier{
Fqn: "https://example.com/attr/classification/value/secret",
},
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Added trigger: %s\n", resp.GetTrigger().GetId())
}
List Obligation Triggers
resp, err := client.Obligations.ListObligationTriggers(context.Background(),
&obligations.ListObligationTriggersRequest{
NamespaceFqn: "https://example.com",
},
)
if err != nil {
log.Fatal(err)
}
for _, trigger := range resp.GetTriggers() {
log.Printf("Trigger ID: %s\n", trigger.GetId())
}
Remove an Obligation Trigger
resp, err := client.Obligations.RemoveObligationTrigger(context.Background(),
&obligations.RemoveObligationTriggerRequest{
Id: "7e8f9a0b-...",
},
)
if err != nil {
log.Fatal(err)
}
log.Printf("Removed trigger: %s\n", resp.GetTrigger().GetId())