gitlab-glaz 1.0.0-aarch64-linux-gnu → 1.1.0-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Cargo.toml CHANGED
@@ -4,12 +4,14 @@ members = ["ext/*"]
4
4
  resolver = "2"
5
5
 
6
6
  [workspace.package]
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  edition = "2024"
9
9
 
10
10
  # Shared dependencies for all crates
11
11
  [workspace.dependencies]
12
12
  magnus = { version = "0.8.2", features = ["rb-sys"] }
13
13
  rb-sys = "0.9.128"
14
- glaz-module = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "13a32ad3f0e6490384afb6ba6a3c3e2a6439901a" }
15
- glaz-roles = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "13a32ad3f0e6490384afb6ba6a3c3e2a6439901a" }
14
+ prost = "0.14"
15
+ glaz-module = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "af9019a186fbb42cede0ad5ba7496aff2f88cb41" }
16
+ glaz-roles = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "af9019a186fbb42cede0ad5ba7496aff2f88cb41" }
17
+ glaz-proto = { git = "https://gitlab.com/gitlab-org/auth/glaz.git", rev = "af9019a186fbb42cede0ad5ba7496aff2f88cb41" }
data/README.md CHANGED
@@ -35,7 +35,7 @@ engine.load_policies(new_cedar_policies)
35
35
 
36
36
  The schema is loaded once at construction and cannot change; build a new engine to use a new schema.
37
37
 
38
- Schemas must declare `User` and `Resource` entities (checks build `User::"<subject_uuid>"` and `Resource::"<object_uuid>"` UIDs), and every action's context type must include `user_permissions: Set<Action>`, which the engine injects with the checked action.
38
+ Schemas must declare `User` and `Resource` entities (checks build `User::"<subject_uuid>"` and `Resource::"<object_uuid>"` UIDs), and every action's context type must include `permissions: Set<Action>`, which the engine injects with the checked action.
39
39
 
40
40
  ## Development
41
41
 
data/ext/glaz/Cargo.toml CHANGED
@@ -9,5 +9,7 @@ crate-type = ["cdylib"]
9
9
 
10
10
  [dependencies]
11
11
  glaz-module = { workspace = true }
12
+ glaz-proto = { workspace = true }
12
13
  glaz-roles = { workspace = true }
13
14
  magnus = { workspace = true }
15
+ prost = { workspace = true }
data/ext/glaz/src/lib.rs CHANGED
@@ -1,4 +1,5 @@
1
1
  use magnus::{Error, Ruby, prelude::*};
2
+ use prost::Message;
2
3
 
3
4
  /// Thin 1:1 wrapper around `glaz_module::PermissionCheckEngine`. The engine
4
5
  /// starts empty; `load_schema` must be called before `load_policies`, and
@@ -51,6 +52,69 @@ impl PermissionCheckEngine {
51
52
  }
52
53
  }
53
54
 
55
+ /// Thin 1:1 wrapper around `glaz_module::GovernPolicyCheckEngine`, the Rego
56
+ /// governance evaluator. Stateless between calls; both methods take an
57
+ /// encoded `glaz.govern.v1.EvaluateGovernPolicyRequest`. `evaluate` raises
58
+ /// on failure (matching `PermissionCheckEngine`'s convention) and returns
59
+ /// the encoded `EvaluateGovernPolicyResponse` on success — `ArgumentError`
60
+ /// for bad policy/input, `RuntimeError` for an engine-side fault, see
61
+ /// `map_error`; `debug_evaluate` is infallible and carries failures in-band
62
+ /// in its JSON response instead, since it's a raw debugging escape hatch.
63
+ ///
64
+ /// Registered as `Glaz::Native::GovernPolicyEngine` (not
65
+ /// `Glaz::GovernPolicyEngine`) specifically to avoid colliding, in name,
66
+ /// with the polished `Gitlab::Glaz::GovernPolicyEngine` wrapper — this
67
+ /// class is raw FFI plumbing; prefer the wrapper unless you specifically
68
+ /// need `debug_evaluate`.
69
+ #[magnus::wrap(class = "Glaz::Native::GovernPolicyEngine")]
70
+ struct GovernPolicyEngine {
71
+ engine: glaz_module::GovernPolicyCheckEngine,
72
+ }
73
+
74
+ impl GovernPolicyEngine {
75
+ fn initialize() -> Self {
76
+ Self {
77
+ engine: glaz_module::GovernPolicyCheckEngine::new(),
78
+ }
79
+ }
80
+
81
+ /// Evaluate using the request's `query` field. `query` must be
82
+ /// non-blank; a blank query is reported in-band as an error rather than
83
+ /// falling back to discovery (use `evaluate` for that).
84
+ fn debug_evaluate(
85
+ ruby: &Ruby,
86
+ rb_self: &Self,
87
+ proto_bytes: magnus::RString,
88
+ ) -> Result<magnus::RString, Error> {
89
+ let bytes = unsafe { proto_bytes.as_slice() };
90
+ let response = rb_self.engine.debug_evaluate(bytes);
91
+ Ok(ruby.str_from_slice(&response))
92
+ }
93
+
94
+ /// Evaluate by auto-discovering the policy's `violation`, `deny`,
95
+ /// and/or `allow` rules. The request's `query` field is ignored.
96
+ ///
97
+ /// Raises on any evaluation failure rather than returning an in-band
98
+ /// error, matching `PermissionCheckEngine#check_action`'s convention.
99
+ /// `map_error` raises `ArgumentError` for malformed protobuf, invalid
100
+ /// policy, oversized documents, undefined results, or an unrecognized
101
+ /// result shape - all caller-fixable - and `RuntimeError` for an
102
+ /// engine-side fault (e.g. the Rego evaluation time budget was
103
+ /// exceeded), which is not.
104
+ fn evaluate(
105
+ ruby: &Ruby,
106
+ rb_self: &Self,
107
+ proto_bytes: magnus::RString,
108
+ ) -> Result<magnus::RString, Error> {
109
+ let bytes = unsafe { proto_bytes.as_slice() };
110
+ let response = rb_self
111
+ .engine
112
+ .evaluate(bytes)
113
+ .map_err(|e| map_error(ruby, e))?;
114
+ Ok(ruby.str_from_slice(&response.encode_to_vec()))
115
+ }
116
+ }
117
+
54
118
  /// Return the default roles embedded in the extension at build time (parsed
55
119
  /// from glaz-roles' roles/**/*.yml) as an array of hashes shaped like
56
120
  /// `{ id: String, name: String, permissions: [String, ...] }`.
@@ -100,5 +164,13 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
100
164
  "check_action",
101
165
  magnus::method!(PermissionCheckEngine::check_action, 1),
102
166
  )?;
167
+ let native = glaz.define_module("Native")?;
168
+ let govern = native.define_class("GovernPolicyEngine", ruby.class_object())?;
169
+ govern.define_singleton_method("new", magnus::function!(GovernPolicyEngine::initialize, 0))?;
170
+ govern.define_method(
171
+ "debug_evaluate",
172
+ magnus::method!(GovernPolicyEngine::debug_evaluate, 1),
173
+ )?;
174
+ govern.define_method("evaluate", magnus::method!(GovernPolicyEngine::evaluate, 1))?;
103
175
  Ok(())
104
176
  }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -15,9 +15,9 @@ module Gitlab
15
15
  # each checkable action with `appliesTo { principal: [User], resource:
16
16
  # [Resource], ... }`, because the engine builds `User::"<subject_uuid>"` and
17
17
  # `Resource::"<object_uuid>"` entity UIDs for every check. The engine also
18
- # injects `user_permissions` (a Set<Action> containing the checked action)
18
+ # injects `permissions` (a Set<Action> containing the checked action)
19
19
  # into the Cedar context, so an action's declared context type must include
20
- # `user_permissions: Set<Action>` alongside any custom attributes.
20
+ # `permissions: Set<Action>` alongside any custom attributes.
21
21
  class Engine
22
22
  # @param schema [String] Cedar schema source (`.cedarschema` DSL).
23
23
  # @param policies [String] Cedar policy source, validated against the
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Gitlab
6
+ module Glaz
7
+ # Wraps a native Glaz::Native::GovernPolicyEngine instance - the Rego
8
+ # governance policy evaluator.
9
+ #
10
+ # The evaluator is a pure function over its arguments: it performs no
11
+ # I/O, holds no state between calls, and executes no actions. The caller
12
+ # supplies the policy's Rego source and the assembled context document,
13
+ # and is responsible for executing the returned actions.
14
+ #
15
+ # +reasons+ and +actions+ are independent, not 1:1: +reasons+ reports why
16
+ # the policy matched - one entry per `violation`/`deny` element the
17
+ # policy produced; empty means it did not match - while +actions+
18
+ # reports what to enforce as a result. Actions are conceptually owned by
19
+ # the policy's configuration in the future central Policy Store, so any
20
+ # match currently yields a single synthesized block action regardless of
21
+ # how many reasons fired. +matched+ is a convenience boolean mirroring
22
+ # "+actions+ is non-empty", for callers who don't want to infer that
23
+ # from array emptiness themselves.
24
+ #
25
+ # #evaluate is the primary path: it always auto-discovers the policy's
26
+ # `violation`/`deny`/`allow` rules (the native +evaluate+ path), returns
27
+ # the hardened Hash shape described below on success, and raises on any
28
+ # evaluation failure - the native call itself raises ArgumentError for
29
+ # bad policy/input and RuntimeError for an engine-side fault; see
30
+ # #evaluate's own doc comment.
31
+ # #debug_evaluate is a separate, secondary method for debugging/testing a Rego query
32
+ # directly: it performs no violation/deny/allow interpretation, does
33
+ # not raise, and returns whatever the query evaluated to (plus any
34
+ # error, in-band) with no guarantee about its shape.
35
+ class GovernPolicyEngine
36
+ def initialize
37
+ @native = ::Glaz::Native::GovernPolicyEngine.new
38
+ end
39
+
40
+ # Evaluate a Rego governance policy against context.
41
+ #
42
+ # @param policy_rego [String] Rego policy text (max 64 KiB).
43
+ # @param context [Hash] the host-assembled context, passed to the
44
+ # policy as the Rego `input` document (max 1 MiB as JSON).
45
+ # @param data [Hash] optional precomputed Rego `data` document (e.g.
46
+ # cached settings or scan results). Top-level keys must not collide
47
+ # with the policy's package path.
48
+ # @raise [ArgumentError] for a caller-fixable evaluation failure -
49
+ # malformed policy, oversized documents, an unrecognized
50
+ # violation/deny/allow shape, and so on.
51
+ # @raise [RuntimeError] for an engine-side fault, e.g. the Rego
52
+ # evaluation time budget was exceeded - not the caller's fault.
53
+ # Callers can rely on a successful return meaning the policy
54
+ # evaluated cleanly; there is no in-band error to check.
55
+ # @return [Hash] `{ matched: Boolean, actions: Array<Hash>, reasons:
56
+ # Array<Hash> }`. `matched` is `true` exactly when `actions`
57
+ # (equivalently `reasons`) is non-empty - a convenience so callers
58
+ # don't have to infer "did the policy fire" from array emptiness.
59
+ # It is not itself an allow/deny decision: today every action is
60
+ # `"block"`, so `matched` and "should deny" coincide, but a future
61
+ # Policy Store action type (e.g. `"log"`) could match without
62
+ # implying denial - check `actions` for enforcement decisions. Each
63
+ # action is `{ action_type: String, message: String | nil, params:
64
+ # Hash }`; each reason is `{ message: String | nil, details: Hash
65
+ # }`. Empty +actions+ means the policy allows the operation.
66
+ def evaluate(policy_rego:, context:, data: {})
67
+ encoded = ::Glaz::Govern::V1::EvaluateGovernPolicyRequest.encode(
68
+ ::Glaz::Govern::V1::EvaluateGovernPolicyRequest.new(
69
+ policy_rego: policy_rego,
70
+ input_json: context.to_json,
71
+ data_json: data.nil? || data.empty? ? "" : data.to_json
72
+ )
73
+ )
74
+
75
+ response = ::Glaz::Govern::V1::EvaluateGovernPolicyResponse.decode(
76
+ @native.evaluate(encoded)
77
+ )
78
+
79
+ {
80
+ matched: response.matched,
81
+ reasons: response.reasons.map { |reason| reason_hash(reason) },
82
+ actions: response.actions.map { |action| action_hash(action) }
83
+ }
84
+ end
85
+
86
+ # Evaluate a Rego query directly, with no `violation`/`deny`/`allow`
87
+ # interpretation. A debugging/testing escape hatch for exercising a
88
+ # policy's Rego directly; prefer #evaluate for the primary path.
89
+ # Unlike #evaluate, this does not raise on failure - +error+ stays
90
+ # in-band, since this method is meant for inspecting raw results
91
+ # (including failures) while developing a policy, not for production
92
+ # decision-making.
93
+ #
94
+ # @param policy_rego [String] Rego policy text (max 64 KiB).
95
+ # @param context [Hash] the host-assembled context, passed to the
96
+ # policy as the Rego `input` document (max 1 MiB as JSON).
97
+ # @param query [String] Rego query to evaluate (e.g.
98
+ # "data.mypackage.violation"); must not be blank.
99
+ # @param data [Hash] optional precomputed Rego `data` document.
100
+ # @return [Hash] `{ result: Object, error: String | nil }` - `result`
101
+ # is exactly whatever `query` evaluated to (parsed JSON), with no
102
+ # guarantee about its shape. A non-nil `error` means evaluation
103
+ # failed; `result` is then `nil`.
104
+ def debug_evaluate(policy_rego:, context:, query:, data: {})
105
+ encoded = ::Glaz::Govern::V1::EvaluateGovernPolicyDebugRequest.encode(
106
+ ::Glaz::Govern::V1::EvaluateGovernPolicyDebugRequest.new(
107
+ policy_rego: policy_rego,
108
+ query: query.to_s,
109
+ input_json: context.to_json,
110
+ data_json: data.nil? || data.empty? ? "" : data.to_json
111
+ )
112
+ )
113
+
114
+ response = JSON.parse(@native.debug_evaluate(encoded), symbolize_names: true)
115
+
116
+ {
117
+ result: response[:result],
118
+ error: response[:error].nil? || response[:error].empty? ? nil : response[:error]
119
+ }
120
+ end
121
+
122
+ private
123
+
124
+ def action_hash(action)
125
+ {
126
+ action_type: action.action_type,
127
+ message: action.message.empty? ? nil : action.message,
128
+ params: JSON.parse(action.params_json)
129
+ }
130
+ end
131
+
132
+ def reason_hash(reason)
133
+ {
134
+ message: reason.message.empty? ? nil : reason.message,
135
+ details: JSON.parse(reason.details_json)
136
+ }
137
+ end
138
+ end
139
+ end
140
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Glaz
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
data/lib/gitlab/glaz.rb CHANGED
@@ -3,10 +3,12 @@
3
3
  require_relative "glaz/version"
4
4
  require_relative 'glaz/loader'
5
5
  require "proto/service_pb"
6
+ require "proto/govern_pb"
6
7
 
7
8
  load_rust_extension
8
9
 
9
10
  require_relative "glaz/engine"
11
+ require_relative "glaz/govern_policy_engine"
10
12
 
11
13
  module Gitlab
12
14
  module Glaz
@@ -21,5 +23,13 @@ module Gitlab
21
23
  role.freeze
22
24
  end.freeze
23
25
  end
26
+
27
+ # Returns the shared Gitlab::Glaz::GovernPolicyEngine instance, memoized
28
+ # across calls. Evaluate Rego governance policies via
29
+ # +govern_policy_engine.evaluate+; see GovernPolicyEngine#evaluate for
30
+ # the full contract.
31
+ def govern_policy_engine
32
+ @govern_policy_engine ||= GovernPolicyEngine.new
33
+ end
24
34
  end
25
35
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: proto/govern.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+
8
+ descriptor_data = "\n\x12proto/govern.proto\x12\x0eglaz.govern.v1\"Y\n\x1b\x45valuateGovernPolicyRequest\x12\x13\n\x0bpolicy_rego\x18\x01 \x01(\t\x12\x12\n\ninput_json\x18\x02 \x01(\t\x12\x11\n\tdata_json\x18\x03 \x01(\t\"m\n EvaluateGovernPolicyDebugRequest\x12\x13\n\x0bpolicy_rego\x18\x01 \x01(\t\x12\r\n\x05query\x18\x02 \x01(\t\x12\x12\n\ninput_json\x18\x03 \x01(\t\x12\x11\n\tdata_json\x18\x04 \x01(\t\"I\n\x0cGovernAction\x12\x13\n\x0b\x61\x63tion_type\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x13\n\x0bparams_json\x18\x03 \x01(\t\"5\n\x0cGovernReason\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x14\n\x0c\x64\x65tails_json\x18\x02 \x01(\t\"\x8d\x01\n\x1c\x45valuateGovernPolicyResponse\x12-\n\x07\x61\x63tions\x18\x01 \x03(\x0b\x32\x1c.glaz.govern.v1.GovernAction\x12-\n\x07reasons\x18\x02 \x03(\x0b\x32\x1c.glaz.govern.v1.GovernReason\x12\x0f\n\x07matched\x18\x03 \x01(\x08\x62\x06proto3"
9
+
10
+ pool = ::Google::Protobuf::DescriptorPool.generated_pool
11
+ pool.add_serialized_file(descriptor_data)
12
+
13
+ module Glaz
14
+ module Govern
15
+ module V1
16
+ EvaluateGovernPolicyRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.govern.v1.EvaluateGovernPolicyRequest").msgclass
17
+ EvaluateGovernPolicyDebugRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.govern.v1.EvaluateGovernPolicyDebugRequest").msgclass
18
+ GovernAction = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.govern.v1.GovernAction").msgclass
19
+ GovernReason = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.govern.v1.GovernReason").msgclass
20
+ EvaluateGovernPolicyResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("glaz.govern.v1.EvaluateGovernPolicyResponse").msgclass
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-glaz
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: aarch64-linux-gnu
6
6
  authors:
7
7
  - group::authorization
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-07 00:00:00.000000000 Z
11
+ date: 2026-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -102,8 +102,10 @@ files:
102
102
  - lib/gitlab/glaz/3.4/glaz.so
103
103
  - lib/gitlab/glaz/4.0/glaz.so
104
104
  - lib/gitlab/glaz/engine.rb
105
+ - lib/gitlab/glaz/govern_policy_engine.rb
105
106
  - lib/gitlab/glaz/loader.rb
106
107
  - lib/gitlab/glaz/version.rb
108
+ - lib/proto/govern_pb.rb
107
109
  - lib/proto/relationships/relationships_pb.rb
108
110
  - lib/proto/service_pb.rb
109
111
  homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-glaz