soar-policy-access_manager 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Dockerfile.policies +6 -0
- data/Dockerfile.tests +14 -0
- data/Gemfile +13 -0
- data/README.md +183 -0
- data/config.ru +37 -0
- data/docker-compose.test.yml +30 -0
- data/docker-compose.yml +14 -0
- data/lib/soar/policy/access_manager/error.rb +10 -0
- data/lib/soar/policy/access_manager/model.rb +23 -0
- data/lib/soar/policy/access_manager/model_provider/policy.rb +94 -0
- data/lib/soar/policy/access_manager/model_provider/service_registry.rb +99 -0
- data/lib/soar/policy/access_manager/model_provider/stub.rb +95 -0
- data/lib/soar/policy/access_manager/test/orchestration_provider/policy.rb +68 -0
- data/lib/soar/policy/access_manager/test/orchestration_provider/service_registry.rb +103 -0
- data/lib/soar/policy/access_manager/test/orchestration_provider/stub.rb +64 -0
- data/lib/soar/policy/access_manager/test/orchestrator.rb +40 -0
- data/soar-policy-access_manager.gemspec +32 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 25513f1e46e2e8a111b246d653f94550937ea03c
|
4
|
+
data.tar.gz: a91e20fb7a8360de3c25dfdaddfa85734480c74c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e8c7cb96934366974567a139347a70a7f978042e90fba5e6c054bef1ebaf1b54a101f40d68aef4b95958148d9059f2d8a17ac7b852c497d5039a925516f5f4e8
|
7
|
+
data.tar.gz: deecf71bd5b3a85fb89210c9e31b1302f19b449eee5b8994b021c16a67489044cdbb48cdbc1b9d9bb97fd73b1fbdcaef65728d3374beac1dfd27c9a97a0211aa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
soar-policy-access_manager
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.3.0
|
data/Dockerfile.policies
ADDED
data/Dockerfile.tests
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
FROM ruby:2.3.1
|
2
|
+
ARG USER_ID
|
3
|
+
ARG USER_NAME
|
4
|
+
RUN useradd -u $USER_ID $USER_NAME -m
|
5
|
+
RUN gem install bundler
|
6
|
+
RUN chown -R $USER_NAME:$USER_NAME /usr/local
|
7
|
+
USER $USER_NAME
|
8
|
+
WORKDIR /usr/local/src
|
9
|
+
ADD Gemfile /usr/local/src/
|
10
|
+
ADD soar-policy-access_manager.gemspec /usr/local/src/
|
11
|
+
RUN bundle install --without development --with test
|
12
|
+
VOLUME /usr/local/src
|
13
|
+
CMD bundle exec cucumber && bundle exec rspec
|
14
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
# SoarPolicyAccessManager
|
2
|
+
|
3
|
+
This Access Manager adheres to SoarAm::AmApi. It is initialized with a soar_sr service registy client (https://rubygems.org/gems/soar_sr)
|
4
|
+
|
5
|
+
This access manager denies access for unauthenticated requests, that is, request that do not have request.session['user'] set. If set, this access manager then queries the service registry for meta regarding the service identifier in question.
|
6
|
+
|
7
|
+
If the service meta indicates no policy, the request is allowed. It the service meta indicates a policy, the policy service is asked, given the authenticated subject identifier, service identifier, resource identifier and request parameters, whether the request should be allowed. This access manager then allows / denies accordingly.
|
8
|
+
|
9
|
+
In the case of the service not found in the service registry, or a failure of any kind, the request is denied. In the interest of security, exceptions while authorizing will be swallowed by this access manager. Only the last exception message will be reported to STDERR.
|
10
|
+
|
11
|
+
## Quickstart
|
12
|
+
When using soar_sc the [soar_authorization](https://github.com/hetznerZA/soar_authorization) gem automagically passes values set in [your router](https://github.com/hetznerZA/soar_sc_routing) as post parameters in authorization requests to policies.
|
13
|
+
|
14
|
+
SoarSc Routing params | SoarAuthorization params | AccessManager Params | Policy Params
|
15
|
+
----------------------|---------------------------|---------------------------|-----------------------
|
16
|
+
service_name | | service_identifier | requestor_identifier
|
17
|
+
path | | resource_identifier | resource_identifier
|
18
|
+
| authentication.identifier | authentication_identifier | subject_identifier
|
19
|
+
|
20
|
+
### Stub provider
|
21
|
+
> Use this provider during tests and development when you don't want any http policy authorization requests.
|
22
|
+
|
23
|
+
Create meta variable mapping services to policy identifiers (names)
|
24
|
+
```ruby
|
25
|
+
> meta = {
|
26
|
+
'service_identifier1' => {
|
27
|
+
'policy' => 'policy1'
|
28
|
+
}
|
29
|
+
}
|
30
|
+
```
|
31
|
+
Create policies variable mapping policy identifiers to resources. Access to a resource is allowed if your authentication identifier (uuid) is present in the array.
|
32
|
+
```ruby
|
33
|
+
> policies = {
|
34
|
+
'policy1' => {
|
35
|
+
'/resource_identifier1' => ['authentication_identifier1'],
|
36
|
+
'/resource_identifier2' => []
|
37
|
+
}
|
38
|
+
}
|
39
|
+
```
|
40
|
+
Instantiate the stub provider and pass it to the access manager model.
|
41
|
+
```ruby
|
42
|
+
> provider = Soar::Policy::AccessManager::ModelProvider::Stub.new(meta: meta, policies: policies})
|
43
|
+
> policy_am = Soar::Policy::AccessManager::Model.new(provider)
|
44
|
+
> puts policy_am.authorized?({
|
45
|
+
service_identifier: 'service_identifier1',
|
46
|
+
resource_identifier: '/resource_identifier1',
|
47
|
+
request: {
|
48
|
+
params: {},
|
49
|
+
authentication_identifier: 'authentication_identifier1'
|
50
|
+
}
|
51
|
+
})
|
52
|
+
```
|
53
|
+
|
54
|
+
### Policy provider
|
55
|
+
> Use this provider during tests and development to send authorization requests directly to a policy.
|
56
|
+
|
57
|
+
Create meta variable mapping services to policy identifiers (names)
|
58
|
+
```ruby
|
59
|
+
> meta = {
|
60
|
+
'service_identifier1' => 'policy1',
|
61
|
+
'service_identifier2' => 'policy2'
|
62
|
+
}
|
63
|
+
```
|
64
|
+
Create policies variable mapping policy identifiers to policy endpoints.
|
65
|
+
```ruby
|
66
|
+
> policies = {
|
67
|
+
'policy1' => 'http://localhost:8080/allow',
|
68
|
+
'policy2' => 'http://localhost:8080/deny'
|
69
|
+
}
|
70
|
+
```
|
71
|
+
|
72
|
+
Instantiate the stub provider and pass it to the access manager model.
|
73
|
+
```ruby
|
74
|
+
> provider = Soar::Policy::AccessManager::ModelProvider::Policy.new(meta: meta, policies: policies})
|
75
|
+
> policy_am = Soar::Policy::AccessManager::Model.new(provider)
|
76
|
+
> puts policy_am.authorized?({
|
77
|
+
service_identifier: 'service_identifier1',
|
78
|
+
resource_identifier: '',
|
79
|
+
request: {
|
80
|
+
params: {},
|
81
|
+
authentication_identifier: ''
|
82
|
+
}
|
83
|
+
})
|
84
|
+
```
|
85
|
+
|
86
|
+
### Service registry provider
|
87
|
+
> Use this provider during production. It reaches out to the service registry and policies. It's used by soar sc.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
> service_registry = SoarSc::service_registry
|
91
|
+
> provider = Soar::Policy::AccessManager::ModelProvider::ServiceRegistry.new(service_registry)
|
92
|
+
> policy_am = Soar::Policy::AccessManager::Model.new(provider)
|
93
|
+
> puts policy_am.authorized?({
|
94
|
+
service_identifier: 'service_identifier1',
|
95
|
+
resource_identifier: '/path',
|
96
|
+
request: {
|
97
|
+
params: request.params,
|
98
|
+
authentication_identifier: SoarAuthentication::Authentication.new(request).identifier
|
99
|
+
}
|
100
|
+
})
|
101
|
+
```
|
102
|
+
|
103
|
+
This access manager can be used with the SoarAuthorization::Authorize middleware:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
> SoarAuthorization::Authorize::register_access_manager('/path', 'path-service', policy_am)
|
107
|
+
> use SoarAuthorization::Authorize
|
108
|
+
```
|
109
|
+
|
110
|
+
## Tests
|
111
|
+
|
112
|
+
### Local
|
113
|
+
|
114
|
+
#### Unit Tests
|
115
|
+
|
116
|
+
```bash
|
117
|
+
$ bundle exec rspec
|
118
|
+
```
|
119
|
+
|
120
|
+
#### Provider Integration Tests
|
121
|
+
|
122
|
+
##### Stub provider
|
123
|
+
```bash
|
124
|
+
$ TEST_ORCHESTRATION_PROVIDER=Stub bundle exec cucumber
|
125
|
+
```
|
126
|
+
|
127
|
+
##### Service Registry provider
|
128
|
+
|
129
|
+
Serve two policies, *allow all* and *deny all*, locally via file *config.ru* using [Rack webserver](https://rack.github.io/).
|
130
|
+
```bash
|
131
|
+
$ docker-compose up
|
132
|
+
```
|
133
|
+
|
134
|
+
Test providers for the stub and service registry via the model api interface
|
135
|
+
```bash
|
136
|
+
$ TEST_ORCHESTRATION_PROVIDER=ServiceRegistry bundle exec cucumber
|
137
|
+
```
|
138
|
+
|
139
|
+
##### Policy provider
|
140
|
+
|
141
|
+
Serve two policies, *allow all* and *deny all*, locally via file *config.ru* using [Rack webserver](https://rack.github.io/).
|
142
|
+
```bash
|
143
|
+
$ docker-compose up
|
144
|
+
```
|
145
|
+
|
146
|
+
Test providers for the stub and service registry via the model api interface
|
147
|
+
```bash
|
148
|
+
$ TEST_ORCHESTRATION_PROVIDER=Policy bundle exec cucumber
|
149
|
+
```
|
150
|
+
|
151
|
+
### CI
|
152
|
+
```
|
153
|
+
$ USER_NAME=$USER USER_ID=$(id -u) POLICY_HOST=policy:8080 TEST_ORCHESTRATION_PROVIDER=ServiceRegistry docker-compose --file docker-compose.test.yml up --abort-on-container-exit --remove-orphans
|
154
|
+
EXIT_CODE=$(docker ps -a -f "name=soar-policy-access_manager-tests" -q | xargs docker inspect -f "{{ .State.ExitCode }}")
|
155
|
+
exit $EXIT_CODE
|
156
|
+
```
|
157
|
+
|
158
|
+
#### Enviroment Variables explained
|
159
|
+
Sets USER_NAME and USER_ID to your currently logged in user. This is important when you're mounting volumes into a container. Root in the container = root on your local.
|
160
|
+
```bash
|
161
|
+
USER_NAME=$USER
|
162
|
+
USER_ID=$(id -u)
|
163
|
+
```
|
164
|
+
|
165
|
+
Select whether you're testing the *ServiceRegistry* , *Policy* or *Stub* provider. Defaults to *Stub*
|
166
|
+
```bash
|
167
|
+
TEST_ORCHESTRATION_PROVIDER=Stub
|
168
|
+
```
|
169
|
+
|
170
|
+
## Contributing
|
171
|
+
|
172
|
+
Please send feedback and comments to the author at:
|
173
|
+
|
174
|
+
Ernst van Graan <ernst.van.graan@hetzner.co.za>
|
175
|
+
|
176
|
+
This gem is sponsored by Hetzner (Pty) Ltd - http://hetzner.co.za
|
177
|
+
|
178
|
+
## License
|
179
|
+
|
180
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
181
|
+
|
182
|
+
## Resources
|
183
|
+
* [soar_am](https://github.com/hetznerZA/soar_am)
|
data/config.ru
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/server'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Simulates two policies available on the network. One allows all requests, the other, denies all requests.
|
7
|
+
# Used for testing the access manager model with both the stub and service registry providers
|
8
|
+
##
|
9
|
+
class StubPolicies
|
10
|
+
|
11
|
+
def self.call(env)
|
12
|
+
request = Rack::Request.new env
|
13
|
+
case request.path
|
14
|
+
when '/allow'
|
15
|
+
[200, {"Context-Type" => "application/json"}, [{ 'status' => 'success', 'data' => { 'allowed' => true, 'detail' => '', 'idm' => nil, 'rule_set' => 'allow all', 'notifications' => ['Policy approved authorization request'] }}.to_json]]
|
16
|
+
when '/deny'
|
17
|
+
[200, {"Context-Type" => "application/json"}, [{ 'status' => 'success', 'data' => { 'allowed' => false, 'detail' => '', 'idm' => nil, 'rule_set' => 'allow all', 'notifications' => ['Policy rejected authorization request'] }}.to_json]]
|
18
|
+
else
|
19
|
+
[200, {"Context-Type" => "application/json"}, [{ 'status' => 'success', 'data' => { 'notifications' => ['No policy associated with service'] }}.to_json]]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class PrintPolicies
|
26
|
+
def self.call(env)
|
27
|
+
request = Rack::Request.new env
|
28
|
+
[200, {"Context-Type" => "application/json"}, [{
|
29
|
+
"is a post request?" => request.post?,
|
30
|
+
"request path" => request.path,
|
31
|
+
"request params" => request.params
|
32
|
+
}.to_json]]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Rack::Server.start :app => StubPolicies
|
37
|
+
#Rack::Server.start :app => PrintPolicies
|
@@ -0,0 +1,30 @@
|
|
1
|
+
version: "2"
|
2
|
+
services:
|
3
|
+
policy:
|
4
|
+
build:
|
5
|
+
context: .
|
6
|
+
dockerfile: Dockerfile.policies
|
7
|
+
image: soar-policy-access_manager-policy
|
8
|
+
container_name: soar-policy-access_manager-policy
|
9
|
+
expose:
|
10
|
+
- "8080"
|
11
|
+
|
12
|
+
tests:
|
13
|
+
build:
|
14
|
+
context: .
|
15
|
+
dockerfile: Dockerfile.tests
|
16
|
+
args:
|
17
|
+
- USER_NAME
|
18
|
+
- USER_ID
|
19
|
+
image: soar-policy-access_manager-tests
|
20
|
+
container_name: soar-policy-access_manager-tests
|
21
|
+
volumes:
|
22
|
+
- .:/usr/local/src
|
23
|
+
links:
|
24
|
+
- policy
|
25
|
+
environment:
|
26
|
+
- TEST_ORCHESTRATION_PROVIDER=Stub
|
27
|
+
- POLICY_HOST=http://policy:8080
|
28
|
+
|
29
|
+
|
30
|
+
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Soar
|
2
|
+
module Policy
|
3
|
+
module AccessManager
|
4
|
+
class Model
|
5
|
+
|
6
|
+
def initialize(provider)
|
7
|
+
@provider = provider
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# @param service_identifier [String]
|
12
|
+
# @param resource_identifier [String]
|
13
|
+
# @param request [Hash]
|
14
|
+
##
|
15
|
+
def authorized?(service_identifier: nil, resource_identifier: nil, request: nil)
|
16
|
+
@provider.authorized?(service_identifier, resource_identifier, request)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'jsender'
|
2
|
+
require 'json'
|
3
|
+
require 'soar/policy/access_manager/error'
|
4
|
+
|
5
|
+
module Soar
|
6
|
+
module Policy
|
7
|
+
module AccessManager
|
8
|
+
module ModelProvider
|
9
|
+
|
10
|
+
class Policy
|
11
|
+
|
12
|
+
include Jsender
|
13
|
+
|
14
|
+
def initialize(meta: {}, policies: {})
|
15
|
+
@meta = meta
|
16
|
+
@policies = policies
|
17
|
+
end
|
18
|
+
|
19
|
+
def authorized?(service_identifier, resource_identifier, request)
|
20
|
+
notifications = []
|
21
|
+
decision = false
|
22
|
+
|
23
|
+
begin
|
24
|
+
if ENV['RACK_ENV'] == 'development'
|
25
|
+
notifications << 'Authorized in development environment'
|
26
|
+
decision = true
|
27
|
+
end
|
28
|
+
|
29
|
+
policy = get_policy(service_identifier)
|
30
|
+
|
31
|
+
if policy.nil?
|
32
|
+
decision = true
|
33
|
+
notifications << 'No policy associated with service'
|
34
|
+
else
|
35
|
+
decision, detail = ask_policy(policy, request[:authentication_identifier], service_identifier, resource_identifier, request)
|
36
|
+
notifications.concat(detail) if not detail.empty?
|
37
|
+
notifications << 'Policy rejected authorization request' if not decision
|
38
|
+
notifications << 'Policy approved authorization request' if decision
|
39
|
+
end
|
40
|
+
rescue SoarSr::ValidationError => ex
|
41
|
+
notifications << "AccessManager error authorizing #{service_identifier} for #{resource_identifier}: #{ex.message}"
|
42
|
+
decision = false
|
43
|
+
rescue Exception => ex
|
44
|
+
notifications << "AccessManager error authorizing #{service_identifier} for #{resource_identifier}: #{ex.message}"
|
45
|
+
decision = false
|
46
|
+
end
|
47
|
+
|
48
|
+
success(notifications, { 'approved' => decision } )
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def get_policy(service_identifier)
|
54
|
+
@meta[service_identifier]
|
55
|
+
end
|
56
|
+
|
57
|
+
def ask_policy(policy, subject_identifier, service_identifier, resource_identifier, request)
|
58
|
+
notifications = []
|
59
|
+
uri = find_uri(policy)
|
60
|
+
if uri.nil?
|
61
|
+
notifications << "Could not retrieve policy for service"
|
62
|
+
return false, notifications
|
63
|
+
end
|
64
|
+
url = URI.parse(uri)
|
65
|
+
params = {
|
66
|
+
'resource_identifier' => resource_identifier,
|
67
|
+
'subject_identifier' => subject_identifier,
|
68
|
+
'service_identifier' => service_identifier,
|
69
|
+
'request' => {
|
70
|
+
'params' => request['params']
|
71
|
+
}.to_json,
|
72
|
+
'flow_identifier' => request['flow_identifier']
|
73
|
+
}
|
74
|
+
res = Net::HTTP.post_form(url, params)
|
75
|
+
result = JSON.parse(res.body)
|
76
|
+
if result['status'] == 'error'
|
77
|
+
notifications << 'Policy query result was not success'
|
78
|
+
return false, notifications
|
79
|
+
end
|
80
|
+
return result['data']['allowed'], notifications
|
81
|
+
rescue => ex
|
82
|
+
notifications << "Exception while asking policy #{ex.message}"
|
83
|
+
return false, notifications
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_uri(policy)
|
87
|
+
@policies[policy]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'jsender'
|
2
|
+
require "soar_sr"
|
3
|
+
require 'soar/policy/access_manager/error'
|
4
|
+
|
5
|
+
module Soar
|
6
|
+
module Policy
|
7
|
+
module AccessManager
|
8
|
+
module ModelProvider
|
9
|
+
|
10
|
+
class ServiceRegistry
|
11
|
+
|
12
|
+
include Jsender
|
13
|
+
attr_reader :service_registry
|
14
|
+
|
15
|
+
def initialize(service_registry)
|
16
|
+
@service_registry = service_registry
|
17
|
+
end
|
18
|
+
|
19
|
+
def authorized?(service_identifier, resource_identifier, request)
|
20
|
+
notifications = []
|
21
|
+
decision = false
|
22
|
+
|
23
|
+
begin
|
24
|
+
if ENV['RACK_ENV'] == 'development'
|
25
|
+
notifications << 'Authorized in development environment'
|
26
|
+
decision = true
|
27
|
+
end
|
28
|
+
|
29
|
+
meta = @service_registry.services.meta_for_service(service_identifier)
|
30
|
+
policy = meta['policy'] if meta and meta.is_a?(Hash) and meta['policy']
|
31
|
+
|
32
|
+
if policy.nil?
|
33
|
+
decision = true
|
34
|
+
notifications << 'No policy associated with service'
|
35
|
+
else
|
36
|
+
decision, detail = ask_policy(policy, request[:authentication_identifier], service_identifier, resource_identifier, request)
|
37
|
+
notifications.concat(detail) if not detail.empty?
|
38
|
+
notifications << 'Policy rejected authorization request' if not decision
|
39
|
+
notifications << 'Policy approved authorization request' if decision
|
40
|
+
end
|
41
|
+
rescue SoarSr::ValidationError => ex
|
42
|
+
notifications << "AccessManager error authorizing #{service_identifier} for #{resource_identifier}: #{ex.message}"
|
43
|
+
decision = false
|
44
|
+
rescue Exception => ex
|
45
|
+
notifications << "AccessManager error authorizing #{service_identifier} for #{resource_identifier}: #{ex.message}"
|
46
|
+
decision = false
|
47
|
+
end
|
48
|
+
|
49
|
+
success(notifications, { 'approved' => decision } )
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def ask_policy(policy, subject_identifier, service_identifier, resource_identifier, request)
|
55
|
+
notifications = []
|
56
|
+
uri = find_first_uri(policy)
|
57
|
+
if uri.nil?
|
58
|
+
notifications << "Could not retrieve policy for service"
|
59
|
+
return false, notifications
|
60
|
+
end
|
61
|
+
url = URI.parse(uri)
|
62
|
+
params = {
|
63
|
+
'resource_identifier' => resource_identifier,
|
64
|
+
'subject_identifier' => subject_identifier,
|
65
|
+
'service_identifier' => service_identifier,
|
66
|
+
'request' => {
|
67
|
+
'params' => request['params'],
|
68
|
+
}.to_json,
|
69
|
+
'flow_identifier' => request['flow_identifier']
|
70
|
+
}
|
71
|
+
res = Net::HTTP.post_form(url, params)
|
72
|
+
|
73
|
+
result = JSON.parse(res.body)
|
74
|
+
if result['status'] == 'error'
|
75
|
+
notifications << 'Policy query result was not success'
|
76
|
+
return false, notifications
|
77
|
+
end
|
78
|
+
return result['data']['allowed'], notifications
|
79
|
+
rescue => ex
|
80
|
+
notifications << "Exception while asking policy #{ex.message}"
|
81
|
+
return false, notifications
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_first_uri(policy)
|
85
|
+
result = @service_registry.services.service_by_name(policy)
|
86
|
+
return nil if not result['status'] == 'success'
|
87
|
+
return nil if result['data']['services'].nil? or result['data']['services'].first.nil?
|
88
|
+
service = result['data']['services'].first
|
89
|
+
return nil if service[1].nil? or service[1]['uris'].nil?
|
90
|
+
access = service[1]['uris'].first
|
91
|
+
return nil if access.nil? or access[1].nil? or access[1]['access_point'].nil?
|
92
|
+
access[1]['access_point']
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'jsender'
|
2
|
+
|
3
|
+
module Soar
|
4
|
+
module Policy
|
5
|
+
module AccessManager
|
6
|
+
module ModelProvider
|
7
|
+
class Stub
|
8
|
+
|
9
|
+
include Jsender
|
10
|
+
|
11
|
+
##
|
12
|
+
# @param [Hash] meta mapping service identifiers to policy identifiers
|
13
|
+
# @param [Hash] policies, policy identifiers map to resource identifiers, that map to an array of authentication_identifiers that are allowed access
|
14
|
+
##
|
15
|
+
def initialize(meta: {}, policies: {})
|
16
|
+
@meta = meta
|
17
|
+
@policies = policies
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# @param [String] service_identifier
|
22
|
+
# @param [String] resource_identifier
|
23
|
+
# @param [Hash] request
|
24
|
+
# @return [Hash] a jsend hash
|
25
|
+
##
|
26
|
+
def authorized?(service_identifier, resource_identifier, request)
|
27
|
+
notifications = []
|
28
|
+
decision = false
|
29
|
+
|
30
|
+
begin
|
31
|
+
if ENV['RACK_ENV'] == 'development'
|
32
|
+
notifications << 'Authorized in development environment'
|
33
|
+
decision = true
|
34
|
+
end
|
35
|
+
|
36
|
+
meta = get_meta(service_identifier)
|
37
|
+
policy = meta['policy'] if meta and meta.is_a?(Hash) and meta['policy']
|
38
|
+
|
39
|
+
if policy.nil?
|
40
|
+
decision = true
|
41
|
+
notifications << 'No policy associated with service'
|
42
|
+
else
|
43
|
+
decision, detail = ask_policy(policy, request[:authentication_identifier], service_identifier, resource_identifier, request)
|
44
|
+
notifications.concat(detail) if not detail.empty?
|
45
|
+
notifications << 'Policy rejected authorization request' if not decision
|
46
|
+
notifications << 'Policy approved authorization request' if decision
|
47
|
+
end
|
48
|
+
rescue SoarSr::ValidationError => ex
|
49
|
+
notifications << "AccessManager error authorizing #{service_identifier} for #{resource_identifier}: #{ex.message}"
|
50
|
+
decision = false
|
51
|
+
rescue Exception => ex
|
52
|
+
notifications << "AccessManager error authorizing #{service_identifier} for #{resource_identifier}: #{ex.message}"
|
53
|
+
decision = false
|
54
|
+
end
|
55
|
+
success(notifications, { 'approved' => decision } )
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
##
|
61
|
+
# @param [String] service identifier
|
62
|
+
# @return [Hash, nil] policy hash or nil
|
63
|
+
##
|
64
|
+
def get_meta(service_identifier)
|
65
|
+
@meta[service_identifier]
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# @param [String] policy
|
70
|
+
# @param [String] authentication_identifier
|
71
|
+
# @param [String] service_identifier
|
72
|
+
# @param [String] resource_identifier
|
73
|
+
# @param [Hash] request
|
74
|
+
# @return [Bool] result
|
75
|
+
# @return [Array] notifications
|
76
|
+
##
|
77
|
+
def ask_policy(policy, authentication_identifier, service_identifier, resource_identifier, params)
|
78
|
+
notifications = []
|
79
|
+
result = @policies[policy][resource_identifier].include?(authentication_identifier)
|
80
|
+
|
81
|
+
if not result
|
82
|
+
notifications << 'Policy query result was not success'
|
83
|
+
return false, notifications
|
84
|
+
end
|
85
|
+
return result, notifications
|
86
|
+
rescue => ex
|
87
|
+
notifications << "Exception while asking policy #{ex.message}"
|
88
|
+
return false, notifications
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'soar/policy/access_manager/model_provider/policy'
|
2
|
+
require 'soar/policy/access_manager/model'
|
3
|
+
require 'jsender'
|
4
|
+
require 'uri'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
module Soar
|
8
|
+
module Policy
|
9
|
+
module AccessManager
|
10
|
+
module Test
|
11
|
+
module OrchestrationProvider
|
12
|
+
class Policy
|
13
|
+
|
14
|
+
##
|
15
|
+
# for a specific path and service
|
16
|
+
# I want to tell the access manager to query a specific url and paramaters for a policy
|
17
|
+
# service_identifier = service_name in router
|
18
|
+
# resource_identifier = path in router
|
19
|
+
##
|
20
|
+
def initialize
|
21
|
+
policy_host = ENV['POLICY_HOST'] || 'localhost:8080'
|
22
|
+
@meta = {
|
23
|
+
'service_identifier1' => 'allow_policy',
|
24
|
+
'service_identifier2' => 'deny_policy'
|
25
|
+
}
|
26
|
+
@policies = {
|
27
|
+
'allow_policy' => "http://#{policy_host}/allow",
|
28
|
+
'deny_policy' => "http://#{policy_host}/deny"
|
29
|
+
}
|
30
|
+
@request = {
|
31
|
+
authentication_identifier: 'authentication_identifier1',
|
32
|
+
params: {},
|
33
|
+
}
|
34
|
+
@resource_identifier = 'resource_identifier1'
|
35
|
+
end
|
36
|
+
|
37
|
+
def grant_access
|
38
|
+
@service_identifier = 'service_identifier1'
|
39
|
+
end
|
40
|
+
|
41
|
+
def deny_access
|
42
|
+
@service_identifier = 'service_identifier2'
|
43
|
+
end
|
44
|
+
|
45
|
+
def no_policy
|
46
|
+
@service_identifier = 'service_identifier3'
|
47
|
+
end
|
48
|
+
|
49
|
+
def notification
|
50
|
+
@response['data']['notifications']
|
51
|
+
end
|
52
|
+
|
53
|
+
def authorized?
|
54
|
+
model_provider = Soar::Policy::AccessManager::ModelProvider::Policy.new(meta: @meta, policies: @policies )
|
55
|
+
model = Soar::Policy::AccessManager::Model.new(model_provider)
|
56
|
+
@response = model.authorized?(service_identifier: @service_identifier, resource_identifier: @resource_identifier, request: @request)
|
57
|
+
end
|
58
|
+
|
59
|
+
def authorized
|
60
|
+
@response['data']['approved']
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'soar/policy/access_manager/model_provider/service_registry'
|
2
|
+
require 'soar/policy/access_manager/model'
|
3
|
+
require 'jsender'
|
4
|
+
require 'uri'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
module Soar
|
8
|
+
module Policy
|
9
|
+
module AccessManager
|
10
|
+
module Test
|
11
|
+
module OrchestrationProvider
|
12
|
+
class ServiceRegistry
|
13
|
+
|
14
|
+
class Services
|
15
|
+
include Jsender
|
16
|
+
|
17
|
+
def initialize()
|
18
|
+
@policy_host = ENV['POLICY_HOST'] || 'localhost:8080'
|
19
|
+
@meta = {
|
20
|
+
'service_identifier1' => {
|
21
|
+
'policy' => 'allow'
|
22
|
+
},
|
23
|
+
'service_identifier2' => {
|
24
|
+
'policy' => 'deny'
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def service_by_name(policy)
|
30
|
+
case policy
|
31
|
+
when 'allow' # allow policy
|
32
|
+
success_data({
|
33
|
+
"services" => [[{}, { "uris" => [[{}, { "access_point" => "http://#{@policy_host}/allow" }]]}]]
|
34
|
+
})
|
35
|
+
when 'deny' # deny policy
|
36
|
+
success_data({
|
37
|
+
"services" => [[{}, { "uris" => [[{}, { "access_point" => "http://#{@policy_host}/deny" }]]}]]
|
38
|
+
})
|
39
|
+
else # no policy
|
40
|
+
success_data({
|
41
|
+
"services" => [[{}, { "uris" => [[{}, { "access_point" => "http://#{@policy_host}/" }]]}]]
|
42
|
+
})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def meta_for_service(service_identifier)
|
47
|
+
@meta[service_identifier]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class Stub
|
53
|
+
include Jsender
|
54
|
+
|
55
|
+
attr_accessor :services
|
56
|
+
|
57
|
+
def initialize(services)
|
58
|
+
@services = services
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
@resource_identifier = 'resource_identifier1'
|
65
|
+
@request = {
|
66
|
+
params: {},
|
67
|
+
authentication_identifier: 'authentication_identifier1'
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def grant_access
|
72
|
+
@service_identifier = 'service_identifier1'
|
73
|
+
end
|
74
|
+
|
75
|
+
def deny_access
|
76
|
+
@service_identifier = 'service_identifier2'
|
77
|
+
end
|
78
|
+
|
79
|
+
def no_policy
|
80
|
+
@service_identifier = 'service_identifier3'
|
81
|
+
end
|
82
|
+
|
83
|
+
def notification
|
84
|
+
@response['data']['notifications']
|
85
|
+
end
|
86
|
+
|
87
|
+
def authorized?
|
88
|
+
service_registry = Stub.new(Services.new)
|
89
|
+
model_provider = Soar::Policy::AccessManager::ModelProvider::ServiceRegistry.new(service_registry)
|
90
|
+
model = Soar::Policy::AccessManager::Model.new(model_provider)
|
91
|
+
@response = model.authorized?(service_identifier: @service_identifier, resource_identifier: @resource_identifier, request: @request)
|
92
|
+
end
|
93
|
+
|
94
|
+
def authorized
|
95
|
+
@response['data']['approved']
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'soar/policy/access_manager/model_provider/stub'
|
3
|
+
require 'soar/policy/access_manager/model'
|
4
|
+
|
5
|
+
module Soar
|
6
|
+
module Policy
|
7
|
+
module AccessManager
|
8
|
+
module Test
|
9
|
+
module OrchestrationProvider
|
10
|
+
class Stub
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@meta = {
|
14
|
+
'service_identifier1' => {
|
15
|
+
'policy' => 'policy1'
|
16
|
+
}
|
17
|
+
}
|
18
|
+
@policies = {
|
19
|
+
'policy1' => {
|
20
|
+
'resource_identifier1' => ['authentication_identifier1'],
|
21
|
+
'resource_identifier2' => []
|
22
|
+
}
|
23
|
+
}
|
24
|
+
@request = {
|
25
|
+
params: {},
|
26
|
+
authentication_identifier: 'authentication_identifier1'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def grant_access
|
31
|
+
@service_identifier = 'service_identifier1'
|
32
|
+
@resource_identifier = 'resource_identifier1'
|
33
|
+
end
|
34
|
+
|
35
|
+
def deny_access
|
36
|
+
@service_identifier = 'service_identifier1'
|
37
|
+
@resource_identifier = 'resource_identifier2'
|
38
|
+
end
|
39
|
+
|
40
|
+
def no_policy
|
41
|
+
@service_identifier = 'service_identifier2'
|
42
|
+
@resource_identifier = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def notification
|
46
|
+
@response['data']['notifications']
|
47
|
+
end
|
48
|
+
|
49
|
+
def authorized?
|
50
|
+
model_provider = Soar::Policy::AccessManager::ModelProvider::Stub.new(meta: @meta, policies: @policies)
|
51
|
+
model = Soar::Policy::AccessManager::Model.new(model_provider)
|
52
|
+
@response = model.authorized?(service_identifier: @service_identifier, resource_identifier: @resource_identifier, request: @request)
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorized
|
56
|
+
@response['data']['approved']
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Soar
|
2
|
+
module Policy
|
3
|
+
module AccessManager
|
4
|
+
module Test
|
5
|
+
class Orchestrator
|
6
|
+
|
7
|
+
def initialize(orchestration_provider)
|
8
|
+
@orchestration_provider = orchestration_provider
|
9
|
+
end
|
10
|
+
|
11
|
+
def grant_access
|
12
|
+
@orchestration_provider.grant_access
|
13
|
+
end
|
14
|
+
|
15
|
+
def deny_access
|
16
|
+
@orchestration_provider.deny_access
|
17
|
+
end
|
18
|
+
|
19
|
+
def no_policy
|
20
|
+
@orchestration_provider.no_policy
|
21
|
+
end
|
22
|
+
|
23
|
+
def notification
|
24
|
+
@orchestration_provider.notification
|
25
|
+
end
|
26
|
+
|
27
|
+
def authorized?
|
28
|
+
@orchestration_provider.authorized?
|
29
|
+
end
|
30
|
+
|
31
|
+
def authorized
|
32
|
+
@orchestration_provider.authorized
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "soar-policy-access_manager"
|
7
|
+
spec.version = "1.0.0"
|
8
|
+
spec.authors = ["Ernst Van Graan", "Charles Mulder"]
|
9
|
+
spec.email = ["ernst.van.graan@hetzner.co.za", "charles.mulder@hetzner.co.za"]
|
10
|
+
|
11
|
+
spec.summary = %q{Access Manager that uses policy services to determine authorization}
|
12
|
+
spec.description = %q{Access Manager that uses policy services to determine authorization}
|
13
|
+
spec.homepage = "https://github.com/hetznerZA/soar_policy_access_manager"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
# if spec.respond_to?(:metadata)
|
19
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
20
|
+
# else
|
21
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
22
|
+
# end
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_dependency 'soar_sr', "~> 1.1.24"
|
30
|
+
spec.add_dependency "jsender", "~> 0.2.0"
|
31
|
+
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: soar-policy-access_manager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ernst Van Graan
|
8
|
+
- Charles Mulder
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-11-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: soar_sr
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 1.1.24
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 1.1.24
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: jsender
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.2.0
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.2.0
|
42
|
+
description: Access Manager that uses policy services to determine authorization
|
43
|
+
email:
|
44
|
+
- ernst.van.graan@hetzner.co.za
|
45
|
+
- charles.mulder@hetzner.co.za
|
46
|
+
executables: []
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".gitignore"
|
51
|
+
- ".rspec"
|
52
|
+
- ".ruby-gemset"
|
53
|
+
- ".ruby-version"
|
54
|
+
- Dockerfile.policies
|
55
|
+
- Dockerfile.tests
|
56
|
+
- Gemfile
|
57
|
+
- README.md
|
58
|
+
- config.ru
|
59
|
+
- docker-compose.test.yml
|
60
|
+
- docker-compose.yml
|
61
|
+
- lib/soar/policy/access_manager/error.rb
|
62
|
+
- lib/soar/policy/access_manager/model.rb
|
63
|
+
- lib/soar/policy/access_manager/model_provider/policy.rb
|
64
|
+
- lib/soar/policy/access_manager/model_provider/service_registry.rb
|
65
|
+
- lib/soar/policy/access_manager/model_provider/stub.rb
|
66
|
+
- lib/soar/policy/access_manager/test/orchestration_provider/policy.rb
|
67
|
+
- lib/soar/policy/access_manager/test/orchestration_provider/service_registry.rb
|
68
|
+
- lib/soar/policy/access_manager/test/orchestration_provider/stub.rb
|
69
|
+
- lib/soar/policy/access_manager/test/orchestrator.rb
|
70
|
+
- soar-policy-access_manager.gemspec
|
71
|
+
homepage: https://github.com/hetznerZA/soar_policy_access_manager
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.5.1
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Access Manager that uses policy services to determine authorization
|
95
|
+
test_files: []
|