oso-oso 0.13.1 → 0.14.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/ext/oso-oso/lib/libpolar.dylib +0 -0
- data/ext/oso-oso/lib/libpolar.so +0 -0
- data/ext/oso-oso/lib/polar.dll +0 -0
- data/lib/oso/polar.rb +2 -0
- data/lib/oso/polar/errors.rb +4 -1
- data/lib/oso/polar/expression.rb +26 -0
- data/lib/oso/polar/ffi/error.rb +22 -2
- data/lib/oso/polar/ffi/polar.rb +16 -0
- data/lib/oso/polar/host.rb +73 -5
- data/lib/oso/polar/pattern.rb +26 -0
- data/lib/oso/polar/polar.rb +47 -1
- data/lib/oso/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2aee8d8308beb5cb906dff373ef43f8a90c5d3f
|
|
4
|
+
data.tar.gz: a121f6f4b7534f53934f49aab4e634cbbbe7f0db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cb9212a01eaa50005a5d3923632741cfbbfff8c28c42a3c7f30b2cf6bf6aeba58fbe4f4c87ff3ccb43deb21dfd2db6e3f95ce7aded5996fe2eb6cb74d5de162
|
|
7
|
+
data.tar.gz: ef8ec9735a3aba0c18d9da63ea0fda8b36e1d6c6cd3306611b1b126d6587f1381b2e558680058c4c926ed9d736b7fa21c96f92ae179b0df485d24ea3bd1c1791
|
data/Gemfile.lock
CHANGED
|
Binary file
|
data/ext/oso-oso/lib/libpolar.so
CHANGED
|
Binary file
|
data/ext/oso-oso/lib/polar.dll
CHANGED
|
Binary file
|
data/lib/oso/polar.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'oso/polar/errors'
|
|
4
|
+
require 'oso/polar/expression'
|
|
4
5
|
require 'oso/polar/ffi'
|
|
5
6
|
require 'oso/polar/host'
|
|
7
|
+
require 'oso/polar/pattern'
|
|
6
8
|
require 'oso/polar/polar'
|
|
7
9
|
require 'oso/polar/predicate'
|
|
8
10
|
require 'oso/polar/query'
|
data/lib/oso/polar/errors.rb
CHANGED
|
@@ -100,8 +100,11 @@ module Oso
|
|
|
100
100
|
class ApiError < Error; end
|
|
101
101
|
class ParameterError < ApiError; end
|
|
102
102
|
|
|
103
|
+
class ValidationError < Error; end
|
|
104
|
+
class RolesValidationError < Error; end
|
|
105
|
+
|
|
103
106
|
UNEXPECTED_EXPRESSION_MESSAGE = <<~MSG
|
|
104
|
-
|
|
107
|
+
Received Expression from Polar VM. The Expression type is not yet supported in this language.
|
|
105
108
|
|
|
106
109
|
This may mean you performed an operation in your policy over an unbound variable.
|
|
107
110
|
MSG
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oso
|
|
4
|
+
module Polar
|
|
5
|
+
# Polar expression.
|
|
6
|
+
class Expression
|
|
7
|
+
attr_reader :operator, :args
|
|
8
|
+
|
|
9
|
+
# @param operator [String]
|
|
10
|
+
# @param args [Array<Object>]
|
|
11
|
+
def initialize(operator, args)
|
|
12
|
+
@operator = operator
|
|
13
|
+
@args = args
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param other [Expression]
|
|
17
|
+
# @return [Boolean]
|
|
18
|
+
def ==(other)
|
|
19
|
+
operator == other.operator && args == other.args
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @see #==
|
|
23
|
+
alias eql? ==
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/oso/polar/ffi/error.rb
CHANGED
|
@@ -24,14 +24,22 @@ module Oso
|
|
|
24
24
|
#
|
|
25
25
|
# @return [::Oso::Polar::Error] if there's an FFI error.
|
|
26
26
|
# @return [::Oso::Polar::FFIErrorNotFound] if there isn't one.
|
|
27
|
-
def self.get # rubocop:disable Metrics/MethodLength
|
|
27
|
+
def self.get # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
28
28
|
error = Rust.get
|
|
29
29
|
return ::Oso::Polar::FFIErrorNotFound if error.null?
|
|
30
30
|
|
|
31
31
|
error = JSON.parse(error.to_s)
|
|
32
32
|
msg = error['formatted']
|
|
33
33
|
kind, body = error['kind'].first
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
# Not all errors have subkind and details.
|
|
36
|
+
# TODO (gj): This bug may exist in other libraries.
|
|
37
|
+
if body.is_a? Hash
|
|
38
|
+
subkind, details = body.first
|
|
39
|
+
else
|
|
40
|
+
subkind, details = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
35
43
|
case kind
|
|
36
44
|
when 'Parse'
|
|
37
45
|
parse_error(subkind, msg: msg, details: details)
|
|
@@ -41,6 +49,8 @@ module Oso
|
|
|
41
49
|
operational_error(subkind, msg: msg, details: details)
|
|
42
50
|
when 'Parameter'
|
|
43
51
|
api_error(subkind, msg: msg, details: details)
|
|
52
|
+
when 'RolesValidation'
|
|
53
|
+
validation_error(msg, details: details)
|
|
44
54
|
end
|
|
45
55
|
end
|
|
46
56
|
|
|
@@ -121,6 +131,16 @@ module Oso
|
|
|
121
131
|
::Oso::Polar::ApiError.new(msg, details: details)
|
|
122
132
|
end
|
|
123
133
|
end
|
|
134
|
+
|
|
135
|
+
# Map FFI Validation errors into Ruby exceptions.
|
|
136
|
+
#
|
|
137
|
+
# @param msg [String]
|
|
138
|
+
# @param details [Hash<String, Object>]
|
|
139
|
+
# @return [::Oso::Polar::ValidationError] the object converted into the expected format.
|
|
140
|
+
private_class_method def self.validation_error(msg, details:)
|
|
141
|
+
# This is currently the only type of validation error.
|
|
142
|
+
::Oso::Polar::RolesValidationError.new(msg, details: details)
|
|
143
|
+
end
|
|
124
144
|
end
|
|
125
145
|
end
|
|
126
146
|
end
|
data/lib/oso/polar/ffi/polar.rb
CHANGED
|
@@ -12,6 +12,8 @@ module Oso
|
|
|
12
12
|
ffi_lib FFI::LIB_PATH
|
|
13
13
|
|
|
14
14
|
attach_function :new, :polar_new, [], FFI::Polar
|
|
15
|
+
attach_function :enable_roles, :polar_enable_roles, [FFI::Polar], :int32
|
|
16
|
+
attach_function :validate_roles_config, :polar_validate_roles_config, [FFI::Polar, :string], :int32
|
|
15
17
|
attach_function :load, :polar_load, [FFI::Polar, :string, :string], :int32
|
|
16
18
|
attach_function :clear_rules, :polar_clear_rules, [FFI::Polar], :int32
|
|
17
19
|
attach_function :next_inline_query, :polar_next_inline_query, [FFI::Polar, :uint32], FFI::Query
|
|
@@ -33,6 +35,20 @@ module Oso
|
|
|
33
35
|
polar
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
|
39
|
+
def enable_roles
|
|
40
|
+
result = Rust.enable_roles(self)
|
|
41
|
+
process_messages
|
|
42
|
+
raise FFI::Error.get if result.zero?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @raise [FFI::Error] if the FFI call returns an error.
|
|
46
|
+
def validate_roles_config(config)
|
|
47
|
+
result = Rust.validate_roles_config(self, JSON.dump(config))
|
|
48
|
+
process_messages
|
|
49
|
+
raise FFI::Error.get if result.zero?
|
|
50
|
+
end
|
|
51
|
+
|
|
36
52
|
# @param src [String]
|
|
37
53
|
# @param filename [String]
|
|
38
54
|
# @raise [FFI::Error] if the FFI call returns an error.
|
data/lib/oso/polar/host.rb
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
module Oso
|
|
4
4
|
module Polar
|
|
5
|
+
# Ruby code reloaders (i.e. the one used by rails) swap out the value of
|
|
6
|
+
# a constant on code changes. Because of this, we can't reliably call
|
|
7
|
+
# `is_a?` on the constant that was passed to `register_class`.
|
|
8
|
+
#
|
|
9
|
+
# Example (where Foo is a class defined in foo.rb):
|
|
10
|
+
# > klass = Foo
|
|
11
|
+
# > Foo.new.is_a? klass
|
|
12
|
+
# => true
|
|
13
|
+
# > ... user changes foo.rb ...
|
|
14
|
+
# > Foo.new.is_a? klass
|
|
15
|
+
# => false
|
|
16
|
+
#
|
|
17
|
+
# To solve this, when we need to access the class (e.g. during isa), we
|
|
18
|
+
# look it up using const_get, which will always return the up-to-date
|
|
19
|
+
# version of the class.
|
|
20
|
+
class PolarClass
|
|
21
|
+
attr_reader :name, :anon_class
|
|
22
|
+
|
|
23
|
+
def initialize(klass)
|
|
24
|
+
@name = klass.name
|
|
25
|
+
# If the class doesn't have a name, it is anonymous, meaning we should
|
|
26
|
+
# actually store it directly
|
|
27
|
+
@anon_class = klass if klass.name.nil?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get
|
|
31
|
+
return anon_class if anon_class
|
|
32
|
+
|
|
33
|
+
Object.const_get(name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
5
37
|
# Translate between Polar and the host language (Ruby).
|
|
6
38
|
class Host # rubocop:disable Metrics/ClassLength
|
|
7
39
|
protected
|
|
@@ -12,13 +44,18 @@ module Oso
|
|
|
12
44
|
attr_reader :classes
|
|
13
45
|
# @return [Hash<Integer, Object>]
|
|
14
46
|
attr_reader :instances
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
attr_reader :accept_expression
|
|
15
49
|
|
|
16
50
|
public
|
|
17
51
|
|
|
52
|
+
attr_writer :accept_expression
|
|
53
|
+
|
|
18
54
|
def initialize(ffi_polar)
|
|
19
55
|
@ffi_polar = ffi_polar
|
|
20
56
|
@classes = {}
|
|
21
57
|
@instances = {}
|
|
58
|
+
@accept_expression = false
|
|
22
59
|
end
|
|
23
60
|
|
|
24
61
|
def initialize_copy(other)
|
|
@@ -35,7 +72,7 @@ module Oso
|
|
|
35
72
|
def get_class(name)
|
|
36
73
|
raise UnregisteredClassError, name unless classes.key? name
|
|
37
74
|
|
|
38
|
-
classes[name]
|
|
75
|
+
classes[name].get
|
|
39
76
|
end
|
|
40
77
|
|
|
41
78
|
# Store a Ruby class in the {#classes} cache.
|
|
@@ -48,7 +85,7 @@ module Oso
|
|
|
48
85
|
def cache_class(cls, name:)
|
|
49
86
|
raise DuplicateClassAliasError.new name: name, old: get_class(name), new: cls if classes.key? name
|
|
50
87
|
|
|
51
|
-
classes[name] = cls
|
|
88
|
+
classes[name] = PolarClass.new(cls)
|
|
52
89
|
name
|
|
53
90
|
end
|
|
54
91
|
|
|
@@ -73,7 +110,10 @@ module Oso
|
|
|
73
110
|
def get_instance(id)
|
|
74
111
|
raise UnregisteredInstanceError, id unless instance? id
|
|
75
112
|
|
|
76
|
-
instances[id]
|
|
113
|
+
instance = instances[id]
|
|
114
|
+
return instance.get if instance.is_a? PolarClass
|
|
115
|
+
|
|
116
|
+
instance
|
|
77
117
|
end
|
|
78
118
|
|
|
79
119
|
# Cache a Ruby instance in the {#instances} cache, fetching a new id if
|
|
@@ -84,6 +124,8 @@ module Oso
|
|
|
84
124
|
# @return [Integer] the instance ID.
|
|
85
125
|
def cache_instance(instance, id: nil)
|
|
86
126
|
id = ffi_polar.new_id if id.nil?
|
|
127
|
+
# Save the instance as a PolarClass if it is a non-anonymous class
|
|
128
|
+
instance = PolarClass.new(instance) if instance.is_a?(Class)
|
|
87
129
|
instances[id] = instance
|
|
88
130
|
id
|
|
89
131
|
end
|
|
@@ -173,7 +215,16 @@ module Oso
|
|
|
173
215
|
{ 'Call' => { 'name' => value.name, 'args' => value.args.map { |el| to_polar(el) } } }
|
|
174
216
|
when value.instance_of?(Variable)
|
|
175
217
|
# This is supported so that we can query for unbound variables
|
|
176
|
-
{ 'Variable' => value }
|
|
218
|
+
{ 'Variable' => value.name }
|
|
219
|
+
when value.instance_of?(Expression)
|
|
220
|
+
{ 'Expression' => { 'operator' => value.operator, 'args' => value.args.map { |el| to_polar(el) } } }
|
|
221
|
+
when value.instance_of?(Pattern)
|
|
222
|
+
dict = to_polar(value.fields)['value']
|
|
223
|
+
if value.tag.nil?
|
|
224
|
+
{ 'Pattern' => dict }
|
|
225
|
+
else
|
|
226
|
+
{ 'Pattern' => { 'Instance' => { 'tag' => value.tag, 'fields' => dict['Dictionary'] } } }
|
|
227
|
+
end
|
|
177
228
|
else
|
|
178
229
|
{ 'ExternalInstance' => { 'instance_id' => cache_instance(value), 'repr' => value.to_s } }
|
|
179
230
|
end
|
|
@@ -219,7 +270,24 @@ module Oso
|
|
|
219
270
|
when 'Call'
|
|
220
271
|
Predicate.new(value['name'], args: value['args'].map { |a| to_ruby(a) })
|
|
221
272
|
when 'Variable'
|
|
222
|
-
Variable.new(value
|
|
273
|
+
Variable.new(value)
|
|
274
|
+
when 'Expression'
|
|
275
|
+
raise UnexpectedPolarTypeError, tag unless accept_expression
|
|
276
|
+
|
|
277
|
+
args = value['args'].map { |a| to_ruby(a) }
|
|
278
|
+
Expression.new(value['operator'], args)
|
|
279
|
+
when 'Pattern'
|
|
280
|
+
case value.keys.first
|
|
281
|
+
when 'Instance'
|
|
282
|
+
tag = value.values.first['tag']
|
|
283
|
+
fields = value.values.first['fields']['fields'].transform_values { |v| to_ruby(v) }
|
|
284
|
+
Pattern.new(tag, fields)
|
|
285
|
+
when 'Dictionary'
|
|
286
|
+
fields = value.values.first['fields'].transform_values { |v| to_ruby(v) }
|
|
287
|
+
Pattern.new(nil, fields)
|
|
288
|
+
else
|
|
289
|
+
raise UnexpectedPolarTypeError, "#{value.keys.first} variant of Pattern"
|
|
290
|
+
end
|
|
223
291
|
else
|
|
224
292
|
raise UnexpectedPolarTypeError, tag
|
|
225
293
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oso
|
|
4
|
+
module Polar
|
|
5
|
+
# Polar pattern.
|
|
6
|
+
class Pattern
|
|
7
|
+
attr_reader :tag, :fields
|
|
8
|
+
|
|
9
|
+
# @param tag [String]
|
|
10
|
+
# @param fields [Hash<String, Object>]
|
|
11
|
+
def initialize(tag, fields)
|
|
12
|
+
@tag = tag
|
|
13
|
+
@fields = fields
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param other [Pattern]
|
|
17
|
+
# @return [Boolean]
|
|
18
|
+
def ==(other)
|
|
19
|
+
tag == other.tag && fields == other.fields
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @see #==
|
|
23
|
+
alias eql? ==
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/oso/polar/polar.rb
CHANGED
|
@@ -37,6 +37,7 @@ module Oso
|
|
|
37
37
|
def initialize
|
|
38
38
|
@ffi_polar = FFI::Polar.create
|
|
39
39
|
@host = Host.new(ffi_polar)
|
|
40
|
+
@polar_roles_enabled = false
|
|
40
41
|
|
|
41
42
|
# Register global constants.
|
|
42
43
|
register_constant nil, name: 'nil'
|
|
@@ -50,11 +51,48 @@ module Oso
|
|
|
50
51
|
register_class String
|
|
51
52
|
end
|
|
52
53
|
|
|
54
|
+
def enable_roles # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
55
|
+
return if polar_roles_enabled
|
|
56
|
+
|
|
57
|
+
roles_helper = Class.new do
|
|
58
|
+
def self.join(separator, left, right)
|
|
59
|
+
[left, right].join(separator)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
register_constant(roles_helper, name: '__oso_internal_roles_helpers__')
|
|
63
|
+
ffi_polar.enable_roles
|
|
64
|
+
self.polar_roles_enabled = true
|
|
65
|
+
|
|
66
|
+
# validate config
|
|
67
|
+
validation_query_results = []
|
|
68
|
+
loop do
|
|
69
|
+
query = ffi_polar.next_inline_query
|
|
70
|
+
break if query.nil?
|
|
71
|
+
|
|
72
|
+
new_host = host.dup
|
|
73
|
+
new_host.accept_expression = true
|
|
74
|
+
results = Query.new(query, host: new_host).to_a
|
|
75
|
+
raise InlineQueryFailedError, query.source if results.empty?
|
|
76
|
+
|
|
77
|
+
validation_query_results.push results
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# turn bindings back into polar
|
|
81
|
+
validation_query_results = validation_query_results.map do |results|
|
|
82
|
+
results.map do |result|
|
|
83
|
+
{ 'bindings' => result.transform_values { |v| host.to_polar(v) } }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
ffi_polar.validate_roles_config(validation_query_results)
|
|
88
|
+
end
|
|
89
|
+
|
|
53
90
|
# Clear all rules and rule sources from the current Polar instance
|
|
54
91
|
#
|
|
55
92
|
# @return [self] for chaining.
|
|
56
93
|
def clear_rules
|
|
57
94
|
ffi_polar.clear_rules
|
|
95
|
+
ffi_polar.enable_roles if polar_roles_enabled
|
|
58
96
|
self
|
|
59
97
|
end
|
|
60
98
|
|
|
@@ -81,7 +119,7 @@ module Oso
|
|
|
81
119
|
# @raise [InlineQueryFailedError] on the first failed inline query.
|
|
82
120
|
# @raise [Error] if any of the FFI calls raise one.
|
|
83
121
|
# @return [self] for chaining.
|
|
84
|
-
def load_str(str, filename: nil)
|
|
122
|
+
def load_str(str, filename: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
85
123
|
raise NullByteInPolarFileError if str.chomp("\0").include?("\0")
|
|
86
124
|
|
|
87
125
|
ffi_polar.load(str, filename: filename)
|
|
@@ -91,6 +129,13 @@ module Oso
|
|
|
91
129
|
|
|
92
130
|
raise InlineQueryFailedError, next_query.source if Query.new(next_query, host: host).first.nil?
|
|
93
131
|
end
|
|
132
|
+
|
|
133
|
+
# If roles are enabled, re-validate config when new rules are loaded.
|
|
134
|
+
if polar_roles_enabled
|
|
135
|
+
self.polar_roles_enabled = false
|
|
136
|
+
enable_roles
|
|
137
|
+
end
|
|
138
|
+
|
|
94
139
|
self
|
|
95
140
|
end
|
|
96
141
|
|
|
@@ -170,6 +215,7 @@ module Oso
|
|
|
170
215
|
|
|
171
216
|
# @return [FFI::Polar]
|
|
172
217
|
attr_reader :ffi_polar
|
|
218
|
+
attr_accessor :polar_roles_enabled
|
|
173
219
|
|
|
174
220
|
# The R and L in REPL for systems where readline is available.
|
|
175
221
|
def repl_readline(prompt)
|
data/lib/oso/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oso-oso
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oso Security, Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-07-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -133,6 +133,7 @@ files:
|
|
|
133
133
|
- lib/oso/oso.rb
|
|
134
134
|
- lib/oso/polar.rb
|
|
135
135
|
- lib/oso/polar/errors.rb
|
|
136
|
+
- lib/oso/polar/expression.rb
|
|
136
137
|
- lib/oso/polar/ffi.rb
|
|
137
138
|
- lib/oso/polar/ffi/error.rb
|
|
138
139
|
- lib/oso/polar/ffi/message.rb
|
|
@@ -141,6 +142,7 @@ files:
|
|
|
141
142
|
- lib/oso/polar/ffi/query_event.rb
|
|
142
143
|
- lib/oso/polar/ffi/source.rb
|
|
143
144
|
- lib/oso/polar/host.rb
|
|
145
|
+
- lib/oso/polar/pattern.rb
|
|
144
146
|
- lib/oso/polar/polar.rb
|
|
145
147
|
- lib/oso/polar/predicate.rb
|
|
146
148
|
- lib/oso/polar/query.rb
|