Skip to content

Commit ab7d951

Browse files
committed
pivot: remove id, rely on labels only. enable annotations by default
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
1 parent 51b57ee commit ab7d951

17 files changed

Lines changed: 47 additions & 606 deletions

CHANGELOG.md

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
55

66
## Unreleased
77

8-
### Evaluated Rules in Decision Logs and API Responses
9-
10-
Rules can now be annotated with a metadata `id` field. When any loaded policy contains
11-
rules with `id` annotations, the IDs of successfully evaluated rules are automatically
12-
included in decision log events (as `ids`). Additionally, the Data API supports a `?ids`
13-
query parameter to include the same information directly in the response payload.
14-
Duplicate IDs from functions called multiple times are suppressed.
15-
16-
```rego
17-
# METADATA
18-
# id: allow-admin
19-
allow if input.role == "admin"
20-
```
21-
22-
When external rule sources are registered, rule tracking is always enabled so that
23-
externally-provided rules with `id` annotations are recorded.
24-
25-
### Rule Labels in Metadata and Decision Logs
8+
### Rule Labels in Decision Logs
269

2710
Rule annotations now support a `labels` field. When `include_rule_metadata` is enabled,
2811
labels from all successfully evaluated rules are collected and included in each decision
@@ -35,7 +18,6 @@ include_rule_metadata: true
3518

3619
```rego
3720
# METADATA
38-
# id: allow-admin
3921
# labels:
4022
# severity: low
4123
# team: platform
@@ -48,12 +30,8 @@ The resulting decision log entry will contain:
4830
{"rule_labels": [{"severity": "low", "team": "platform"}]}
4931
```
5032

51-
Embedding projects can change the default by setting `config.DefaultIncludeRuleMetadata = true`.
52-
53-
Note: when using `include_rule_metadata` via the configuration file, policies must be loaded
54-
via the bundle plugin (i.e. configured through `services` and `bundles` in the config) for
55-
annotations to be processed correctly. Policies loaded from command-line paths are parsed
56-
before the configuration is available.
33+
The runtime now processes metadata annotations by default, so labels are always
34+
available regardless of how policies are loaded.
5735

5836
## 1.16.1
5937

e2e/cli/evaluated_rules.txtar

Lines changed: 0 additions & 81 deletions
This file was deleted.
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Verify that evaluated rule IDs appear in decision logs written via the
1+
# Verify that rule labels appear in decision logs written via the
22
# file_logger plugin (slog-based path).
33

44
exec $OPA build -o bundles/bundle.tar.gz --bundle policy/
@@ -8,7 +8,7 @@ http_server bundles
88
exec $OPA run --server --addr unix://opa.sock --log-format json --log-level info --config-file config.yaml &opa&
99
retry curl -sf --unix-socket opa.sock 'http://localhost/health?bundles'
1010

11-
# POST to v1/data — rule with id "allow-admin" should be evaluated.
11+
# POST to v1/data — rule with labels should be evaluated.
1212
exec curl -sf --unix-socket opa.sock -H 'Content-Type: application/json' -d '{"input": {"role": "admin"}}' http://localhost/v1/data/authz/allow
1313
jq stdout '.result == true'
1414

@@ -19,11 +19,11 @@ jq stdout '.result == false'
1919
kill -INT opa
2020
wait opa
2121

22-
# Decision log file should contain ids for the admin request.
23-
jq -s decisions.log '[.[] | select(.ids == ["allow-admin"])] | length == 1'
22+
# Decision log file should contain rule_labels for the admin request.
23+
jq -s decisions.log '[.[] | select(.rule_labels == [{"severity": "low"}])] | length == 1'
2424

25-
# Decision log file should have an entry without ids for the guest request.
26-
jq -s decisions.log '[.[] | select(.path == "authz/allow" and (.ids == null))] | length == 1'
25+
# Decision log file should have an entry without rule_labels for the guest request.
26+
jq -s decisions.log '[.[] | select(.path == "authz/allow" and (.rule_labels == null))] | length == 1'
2727

2828
-- config.yaml --
2929
include_rule_metadata: true
@@ -49,11 +49,13 @@ package authz
4949
default allow := false
5050

5151
# METADATA
52-
# id: allow-admin
52+
# labels:
53+
# severity: low
5354
allow if input.role == "admin"
5455

5556
# METADATA
56-
# id: allow-editor
57+
# labels:
58+
# severity: low
5759
allow if input.role == "editor"
5860

5961
-- bundles/.gitkeep --

e2e/cli/rule_metadata_dl.txtar

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ retry curl -sf --unix-socket opa.sock 'http://localhost/health?bundles'
1212
exec curl -sf --unix-socket opa.sock -H 'Content-Type: application/json' -d '{"input": {"role": "admin"}}' http://localhost/v1/data/authz/allow
1313
jq stdout '.result == true'
1414

15-
# Evaluate violations — triggers both violation rules
15+
# Evaluate violations — triggers both violation rules; labels collected per-rule.
1616
exec curl -sf --unix-socket opa.sock -H 'Content-Type: application/json' -d '{"input": {"role": "admin", "dept": "eng"}}' http://localhost/v1/data/authz/violations
1717
jq stdout '.result | length == 2'
1818

@@ -46,26 +46,22 @@ package authz
4646
default allow := false
4747

4848
# METADATA
49-
# id: allow-admin
5049
# labels:
5150
# severity: low
5251
allow if input.role == "admin"
5352

5453
# METADATA
55-
# id: allow-editor
5654
# labels:
5755
# severity: low
5856
allow if input.role == "editor"
5957

6058
# METADATA
61-
# id: no-admin-role
6259
# labels:
6360
# severity: high
6461
# category: compliance
6562
violations contains "admin role is restricted" if input.role == "admin"
6663

6764
# METADATA
68-
# id: no-admin-in-eng
6965
# labels:
7066
# severity: critical
7167
# category: org-policy

v1/ast/annotations.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ type (
2727
// Annotations represents metadata attached to other AST nodes such as rules.
2828
Annotations struct {
2929
Scope string `json:"scope"`
30-
ID string `json:"id,omitempty"`
3130
Title string `json:"title,omitempty"`
3231
Entrypoint bool `json:"entrypoint,omitempty"`
3332
Description string `json:"description,omitempty"`
@@ -135,10 +134,6 @@ func (a *Annotations) Compare(other *Annotations) int {
135134
return cmp
136135
}
137136

138-
if cmp := strings.Compare(a.ID, other.ID); cmp != 0 {
139-
return cmp
140-
}
141-
142137
if cmp := strings.Compare(a.Title, other.Title); cmp != 0 {
143138
return cmp
144139
}
@@ -206,10 +201,6 @@ func (a *Annotations) MarshalJSON() ([]byte, error) {
206201
"scope": a.Scope,
207202
}
208203

209-
if a.ID != "" {
210-
data["id"] = a.ID
211-
}
212-
213204
if a.Title != "" {
214205
data["title"] = a.Title
215206
}
@@ -458,10 +449,6 @@ func (a *Annotations) toObject() (*Object, *Error) {
458449
obj.Insert(InternedTerm("scope"), InternedTerm(a.Scope))
459450
}
460451

461-
if len(a.ID) > 0 {
462-
obj.Insert(InternedTerm("id"), StringTerm(a.ID))
463-
}
464-
465452
if len(a.Title) > 0 {
466453
obj.Insert(InternedTerm("title"), StringTerm(a.Title))
467454
}
@@ -620,10 +607,6 @@ func attachAnnotationsNodes(mod *Module) Errors {
620607
if err := validateAnnotationEntrypointAttachment(a); err != nil {
621608
errs = append(errs, err)
622609
}
623-
624-
if err := validateAnnotationIDAttachment(a); err != nil {
625-
errs = append(errs, err)
626-
}
627610
}
628611

629612
return errs
@@ -656,14 +639,6 @@ func validateAnnotationEntrypointAttachment(a *Annotations) *Error {
656639
return nil
657640
}
658641

659-
func validateAnnotationIDAttachment(a *Annotations) *Error {
660-
if a.ID != "" && a.Scope != annotationScopeRule {
661-
return NewError(
662-
ParseErr, a.Loc(), "annotation id applied to non-rule scope '%v'", a.Scope)
663-
}
664-
return nil
665-
}
666-
667642
// Copy returns a deep copy of a.
668643
func (a *AuthorAnnotation) Copy() *AuthorAnnotation {
669644
cpy := *a

v1/ast/parser.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2815,7 +2815,6 @@ func (p *Parser) validateDefaultRuleArgs(rule *Rule) bool {
28152815
// which isn't handled properly by json for some reason.
28162816
type rawAnnotation struct {
28172817
Scope string `yaml:"scope"`
2818-
ID string `yaml:"id"`
28192818
Title string `yaml:"title"`
28202819
Entrypoint bool `yaml:"entrypoint"`
28212820
Description string `yaml:"description"`
@@ -2881,7 +2880,6 @@ func (b *metadataParser) Parse() (result *Annotations, err error) {
28812880
result = &Annotations{
28822881
comments: b.comments,
28832882
Scope: raw.Scope,
2884-
ID: raw.ID,
28852883
Entrypoint: raw.Entrypoint,
28862884
Title: raw.Title,
28872885
Description: raw.Description,

v1/config/default.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,3 @@ const (
1010
// DefaultMinTLSVersion is the minimum TLS version used by OPA server and REST clients
1111
DefaultMinTLSVersion = tls.VersionTLS12
1212
)
13-
14-
var (
15-
// DefaultIncludeRuleMetadata controls whether custom metadata from rule annotations
16-
// is included in decision logs by default. When true, this also enables annotation
17-
// processing during policy parsing. Embedding projects can set this to true via an
18-
// init() function to change the default.
19-
DefaultIncludeRuleMetadata bool
20-
)

v1/plugins/logs/plugin.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ type EventV1 struct {
6868
Timestamp time.Time `json:"timestamp"`
6969
Metrics map[string]any `json:"metrics,omitempty"`
7070
RequestID uint64 `json:"req_id,omitempty"`
71-
IDs []string `json:"ids,omitempty"`
7271
RuleLabels []map[string]any `json:"rule_labels,omitempty"`
7372
RequestContext *RequestContext `json:"request_context,omitempty"`
7473
Custom map[string]any `json:"custom,omitempty"`
@@ -211,14 +210,6 @@ func (e *EventV1) AST() (ast.Value, error) {
211210
event.Insert(ast.InternedTerm("requested_by"), ast.StringTerm(e.RequestedBy))
212211
}
213212

214-
if len(e.IDs) > 0 {
215-
evaluatedRules := make([]*ast.Term, len(e.IDs))
216-
for i, v := range e.IDs {
217-
evaluatedRules[i] = ast.StringTerm(v)
218-
}
219-
event.Insert(ast.InternedTerm("ids"), ast.ArrayTerm(evaluatedRules...))
220-
}
221-
222213
if len(e.RuleLabels) > 0 {
223214
v, err := ast.InterfaceToValue(e.RuleLabels)
224215
if err == nil {
@@ -746,7 +737,6 @@ func (p *Plugin) Log(ctx context.Context, decision *server.Info) error {
746737
RequestedBy: decision.RemoteAddr,
747738
Timestamp: decision.Timestamp,
748739
RequestID: decision.RequestID,
749-
IDs: decision.EvaluatedRuleIDs,
750740
RuleLabels: decision.EvaluatedRuleLabels,
751741
inputAST: decision.InputAST,
752742
Custom: decision.Custom,
@@ -1242,7 +1232,6 @@ func eventToAttrs(event EventV1) []slog.Attr {
12421232
attrs = append(attrs, slog.Any("request_context", event.RequestContext))
12431233
}
12441234

1245-
addAttrIfSliceNotEmpty(&attrs, "ids", event.IDs)
12461235
addAttrIfSliceNotEmpty(&attrs, "rule_labels", event.RuleLabels)
12471236
addAttrIfHasLen(&attrs, "custom", event.Custom)
12481237

@@ -1364,7 +1353,6 @@ func eventToFields(event EventV1) map[string]any {
13641353
fields["request_context"] = event.RequestContext
13651354
}
13661355

1367-
addIfSliceNotEmpty(fields, "ids", stringsToAny(event.IDs))
13681356

13691357
if len(event.RuleLabels) > 0 {
13701358
var v any = event.RuleLabels

0 commit comments

Comments
 (0)