conjur-rack 1.4.0 → 3.1.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/CHANGELOG.md +25 -0
- data/Gemfile +7 -1
- data/conjur-rack.gemspec +7 -4
- data/jenkins.sh +12 -0
- data/lib/conjur/rack.rb +23 -0
- data/lib/conjur/rack/authenticator.rb +122 -51
- data/lib/conjur/rack/user.rb +71 -17
- data/lib/conjur/rack/version.rb +1 -1
- data/spec/rack/authenticator_spec.rb +155 -83
- data/spec/rack/path_prefix_spec.rb +3 -3
- data/spec/rack/user_spec.rb +115 -47
- data/spec/rack_spec.rb +49 -0
- data/spec/spec_helper.rb +36 -0
- metadata +47 -23
- data/.rvmrc +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ee85c177c59da73e229825170b3b8c34230e4e1
|
|
4
|
+
data.tar.gz: 7758e75373c0ccdfcb61539a33325795d5394600
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ccec8cadd0827fbf5b298f57fa78aa32a7792a58d985c0ff54ee05b80c256e55c0a9c868a1432be4908d7314c9becefdcf812ed95aad28ec756e989ad250def2
|
|
7
|
+
data.tar.gz: 36e507e1714480f2c5e4f5ff9d3af63cca883f855fc2259e272c3ce45420dadc36e0cd64cbe926f6396d0d3793968f802df147950ac0db86c7b69bba3abd0b8d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
# v3.1.0
|
|
2
|
+
|
|
3
|
+
* Support for JWT Slosilo tokens.
|
|
4
|
+
|
|
5
|
+
# v3.0.0.pre
|
|
6
|
+
|
|
7
|
+
* Initial support for Conjur 5.
|
|
8
|
+
|
|
9
|
+
# v2.3.0
|
|
10
|
+
|
|
11
|
+
* Add TRUSTED_PROXIES support
|
|
12
|
+
|
|
13
|
+
# v2.2.0
|
|
14
|
+
|
|
15
|
+
* resolve 'own' token to CONJUR_ACCOUNT env var
|
|
16
|
+
* add #optional paths to Conjur::Rack authenticator
|
|
17
|
+
|
|
18
|
+
# v2.1.0
|
|
19
|
+
|
|
20
|
+
* Add handling for `Conjur-Audit-Roles` and `Conjur-Audit-Resources`
|
|
21
|
+
|
|
22
|
+
# v2.0.0
|
|
23
|
+
|
|
24
|
+
* Change `global_sudo?` to `global_elevate?`
|
|
25
|
+
|
|
1
26
|
# v1.4.0
|
|
2
27
|
|
|
3
28
|
* Add `validated_global_privilege` helper function to get the global privilege, if any, which has been submitted with the request and verified by the Conjur server.
|
data/Gemfile
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
source 'https://rubygems.org'
|
|
2
2
|
|
|
3
|
+
# make sure github uses TLS
|
|
4
|
+
git_source(:github) { |name| "https://github.com/#{name}.git" }
|
|
5
|
+
|
|
6
|
+
#ruby=ruby-2.3.4
|
|
7
|
+
#ruby-gemset=conjur-rack
|
|
8
|
+
|
|
3
9
|
# Specify your gem's dependencies in conjur-rack.gemspec
|
|
4
10
|
gemspec
|
|
5
11
|
|
|
6
|
-
gem 'conjur-api', github: '
|
|
12
|
+
# gem 'conjur-api', github: 'cyberark/conjur-api-ruby', branch: 'master'
|
data/conjur-rack.gemspec
CHANGED
|
@@ -18,12 +18,15 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
spec.add_dependency "slosilo"
|
|
22
|
-
spec.add_dependency "conjur-api", "
|
|
23
|
-
spec.add_dependency "rack"
|
|
21
|
+
spec.add_dependency "slosilo", "~> 2.1"
|
|
22
|
+
spec.add_dependency "conjur-api", "< 6"
|
|
23
|
+
spec.add_dependency "rack", '~> 1'
|
|
24
24
|
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
|
26
26
|
spec.add_development_dependency "rake"
|
|
27
|
-
spec.add_development_dependency "rspec"
|
|
27
|
+
spec.add_development_dependency "rspec"
|
|
28
28
|
spec.add_development_dependency 'ci_reporter_rspec'
|
|
29
|
+
spec.add_development_dependency 'pry-byebug'
|
|
30
|
+
spec.add_development_dependency 'rspec-its'
|
|
31
|
+
|
|
29
32
|
end
|
data/jenkins.sh
ADDED
data/lib/conjur/rack.rb
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
require "conjur/rack/version"
|
|
2
2
|
require "conjur/rack/authenticator"
|
|
3
3
|
require "conjur/rack/path_prefix"
|
|
4
|
+
require 'ipaddr'
|
|
5
|
+
require 'set'
|
|
6
|
+
|
|
7
|
+
module TrustedProxies
|
|
8
|
+
|
|
9
|
+
def trusted_proxy?(ip)
|
|
10
|
+
trusted_proxies ? trusted_proxies.any? { |cidr| cidr.include?(ip) } : super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def trusted_proxies
|
|
14
|
+
@trusted_proxies || ENV['TRUSTED_PROXIES'].try do |proxies|
|
|
15
|
+
cidrs = Set.new(proxies.split(',') + ['127.0.0.1'])
|
|
16
|
+
@trusted_proxies = cidrs.collect {|cidr| IPAddr.new(cidr) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module Rack
|
|
23
|
+
class Request
|
|
24
|
+
prepend TrustedProxies
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -2,36 +2,51 @@ require "conjur/rack/user"
|
|
|
2
2
|
|
|
3
3
|
module Conjur
|
|
4
4
|
module Rack
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def conjur_rack
|
|
8
|
+
Thread.current[:conjur_rack] ||= {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def identity?
|
|
12
|
+
!conjur_rack[:identity].nil?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def user
|
|
16
|
+
User.new(identity[0], identity[1],
|
|
17
|
+
:privilege => privilege,
|
|
18
|
+
:remote_ip => remote_ip,
|
|
19
|
+
:audit_roles => audit_roles,
|
|
20
|
+
:audit_resources => audit_resources
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def identity
|
|
25
|
+
conjur_rack[:identity] or raise "No Conjur identity for current request"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# class attributes
|
|
29
|
+
[:privilege, :remote_ip, :audit_roles, :audit_resources].each do |a|
|
|
30
|
+
define_method(a) do
|
|
31
|
+
conjur_rack[a]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
23
34
|
end
|
|
35
|
+
|
|
24
36
|
|
|
25
37
|
class Authenticator
|
|
26
38
|
class AuthorizationError < SecurityError
|
|
27
39
|
end
|
|
28
40
|
class SignatureError < SecurityError
|
|
29
41
|
end
|
|
42
|
+
class Forbidden < SecurityError
|
|
43
|
+
end
|
|
30
44
|
|
|
31
45
|
attr_reader :app, :options
|
|
32
46
|
|
|
33
47
|
# +options+:
|
|
34
|
-
# :except :: a list of request path patterns for which to skip authentication
|
|
48
|
+
# :except :: a list of request path patterns for which to skip authentication.
|
|
49
|
+
# :optional :: request path patterns for which authentication is optional.
|
|
35
50
|
def initialize app, options = {}
|
|
36
51
|
@app = app
|
|
37
52
|
@options = options
|
|
@@ -39,10 +54,13 @@ module Conjur
|
|
|
39
54
|
|
|
40
55
|
# threadsafe accessors, values are established explicitly below
|
|
41
56
|
def env; Thread.current[:rack_env] ; end
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
|
|
58
|
+
# instance attributes
|
|
59
|
+
[:token, :account, :privilege, :remote_ip, :audit_roles, :audit_resources].each do |a|
|
|
60
|
+
define_method(a) do
|
|
61
|
+
conjur_rack[a]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
46
64
|
|
|
47
65
|
def call rackenv
|
|
48
66
|
# never store request-specific variables as application attributes
|
|
@@ -50,13 +68,19 @@ module Conjur
|
|
|
50
68
|
if authenticate?
|
|
51
69
|
begin
|
|
52
70
|
identity = verify_authorization_and_get_identity # [token, account]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
|
|
72
|
+
if identity
|
|
73
|
+
conjur_rack[:token] = identity[0]
|
|
74
|
+
conjur_rack[:account] = identity[1]
|
|
75
|
+
conjur_rack[:identity] = identity
|
|
76
|
+
conjur_rack[:privilege] = http_privilege
|
|
77
|
+
conjur_rack[:remote_ip] = http_remote_ip
|
|
78
|
+
conjur_rack[:audit_roles] = http_audit_roles
|
|
79
|
+
conjur_rack[:audit_resources] = http_audit_resources
|
|
80
|
+
end
|
|
59
81
|
|
|
82
|
+
rescue Forbidden
|
|
83
|
+
return error 403, $!.message
|
|
60
84
|
rescue SecurityError, RestClient::Exception
|
|
61
85
|
return error 401, $!.message
|
|
62
86
|
end
|
|
@@ -65,61 +89,108 @@ module Conjur
|
|
|
65
89
|
@app.call rackenv
|
|
66
90
|
ensure
|
|
67
91
|
Thread.current[:rack_env] = nil
|
|
68
|
-
Thread.current[:
|
|
69
|
-
Thread.current[:conjur_rack_token] = nil
|
|
70
|
-
Thread.current[:conjur_rack_account] = nil
|
|
71
|
-
Thread.current[:conjur_rack_privilege] = nil
|
|
72
|
-
Thread.current[:conjur_rack_remote_ip] = nil
|
|
92
|
+
Thread.current[:conjur_rack] = {}
|
|
73
93
|
end
|
|
74
94
|
end
|
|
75
95
|
|
|
76
96
|
protected
|
|
77
97
|
|
|
98
|
+
def conjur_rack
|
|
99
|
+
Conjur::Rack.conjur_rack
|
|
100
|
+
end
|
|
101
|
+
|
|
78
102
|
def validate_token_and_get_account token
|
|
79
|
-
failure = SignatureError.new("
|
|
103
|
+
failure = SignatureError.new("Unauthorized: Invalid token")
|
|
80
104
|
raise failure unless (signer = Slosilo.token_signer token)
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
if signer == 'own'
|
|
106
|
+
ENV['CONJUR_ACCOUNT'] or raise failure
|
|
107
|
+
else
|
|
108
|
+
raise failure unless signer =~ /\Aauthn:(.+)\z/
|
|
109
|
+
$1
|
|
110
|
+
end
|
|
83
111
|
end
|
|
84
112
|
|
|
85
113
|
def error status, message
|
|
86
114
|
[status, { 'Content-Type' => 'text/plain', 'Content-Length' => message.length.to_s }, [message] ]
|
|
87
115
|
end
|
|
116
|
+
|
|
117
|
+
def parsed_token
|
|
118
|
+
token = http_authorization.to_s[/^Token token="(.*)"/, 1]
|
|
119
|
+
token = token && JSON.parse(Base64.decode64(token))
|
|
120
|
+
token = Slosilo::JWT token rescue token
|
|
121
|
+
rescue JSON::ParserError
|
|
122
|
+
raise AuthorizationError.new("Malformed authorization token")
|
|
123
|
+
end
|
|
88
124
|
|
|
125
|
+
RECOGNIZED_CLAIMS = [
|
|
126
|
+
'iat', 'exp', # recognized by Slosilo
|
|
127
|
+
'cidr', 'sub',
|
|
128
|
+
'iss', 'aud', 'jti' # RFC 7519, not handled but recognized
|
|
129
|
+
].freeze
|
|
130
|
+
|
|
89
131
|
def verify_authorization_and_get_identity
|
|
90
|
-
if
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
132
|
+
if token = parsed_token
|
|
133
|
+
begin
|
|
134
|
+
account = validate_token_and_get_account token
|
|
135
|
+
if token.respond_to?(:claims)
|
|
136
|
+
claims = token.claims
|
|
137
|
+
raise AuthorizationError, "token contains unrecognized claims" unless \
|
|
138
|
+
(claims.keys.map(&:to_s) - RECOGNIZED_CLAIMS).empty?
|
|
139
|
+
if (cidr = claims['cidr'])
|
|
140
|
+
raise Forbidden, "IP address rejected" unless \
|
|
141
|
+
cidr.map(&IPAddr.method(:new)).any? { |c| c.include? http_remote_ip }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
return [token, account]
|
|
145
|
+
end
|
|
94
146
|
else
|
|
95
|
-
|
|
147
|
+
path = http_path
|
|
148
|
+
if optional_paths.find{|p| p.match(path)}.nil?
|
|
149
|
+
raise AuthorizationError.new("Authorization missing")
|
|
150
|
+
else
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
96
153
|
end
|
|
97
154
|
end
|
|
98
155
|
|
|
99
156
|
def authenticate?
|
|
157
|
+
path = http_path
|
|
100
158
|
if options[:except]
|
|
101
159
|
options[:except].find{|p| p.match(path)}.nil?
|
|
102
160
|
else
|
|
103
161
|
true
|
|
104
162
|
end
|
|
105
163
|
end
|
|
106
|
-
|
|
107
|
-
def conjur_privilege
|
|
108
|
-
env['HTTP_X_CONJUR_PRIVILEGE']
|
|
109
|
-
end
|
|
110
164
|
|
|
111
|
-
def
|
|
165
|
+
def optional_paths
|
|
166
|
+
options[:optional] || []
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def http_authorization
|
|
112
170
|
env['HTTP_AUTHORIZATION']
|
|
113
171
|
end
|
|
114
|
-
|
|
115
|
-
def
|
|
172
|
+
|
|
173
|
+
def http_privilege
|
|
174
|
+
env['HTTP_X_CONJUR_PRIVILEGE']
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def http_remote_ip
|
|
116
178
|
require 'rack/request'
|
|
117
179
|
::Rack::Request.new(env).ip
|
|
118
180
|
end
|
|
119
|
-
|
|
120
|
-
def
|
|
181
|
+
|
|
182
|
+
def http_audit_roles
|
|
183
|
+
env['HTTP_CONJUR_AUDIT_ROLES']
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def http_audit_resources
|
|
187
|
+
env['HTTP_CONJUR_AUDIT_RESOURCES']
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def http_path
|
|
121
191
|
[ env['SCRIPT_NAME'], env['PATH_INFO'] ].join
|
|
122
192
|
end
|
|
193
|
+
|
|
123
194
|
end
|
|
124
195
|
end
|
|
125
196
|
end
|
data/lib/conjur/rack/user.rb
CHANGED
|
@@ -2,25 +2,32 @@ require 'conjur/api'
|
|
|
2
2
|
|
|
3
3
|
module Conjur
|
|
4
4
|
module Rack
|
|
5
|
+
# Token data can be a string (which is the user login), or a Hash.
|
|
6
|
+
# If it's a hash, it should contain the user login keyed by the string 'login'.
|
|
7
|
+
# The rest of the payload is available as +attributes+.
|
|
5
8
|
class User
|
|
6
|
-
|
|
9
|
+
attr_reader :token, :account, :privilege, :remote_ip, :audit_roles, :audit_resources
|
|
7
10
|
|
|
8
|
-
def initialize(token, account,
|
|
11
|
+
def initialize(token, account, options = {})
|
|
9
12
|
@token = token
|
|
10
13
|
@account = account
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
# Third argument used to be the name of privilege, be
|
|
15
|
+
# backwards compatible:
|
|
16
|
+
if options.respond_to?(:to_str)
|
|
17
|
+
@privilege = options
|
|
18
|
+
else
|
|
19
|
+
@privilege = options[:privilege]
|
|
20
|
+
@remote_ip = options[:remote_ip]
|
|
21
|
+
@audit_roles = options[:audit_roles]
|
|
22
|
+
@audit_resources = options[:audit_resources]
|
|
23
|
+
end
|
|
13
24
|
end
|
|
14
25
|
|
|
15
26
|
# This file was accidently calling account conjur_account,
|
|
16
27
|
# I'm adding an alias in case that's going on anywhere else.
|
|
17
28
|
# -- Jon
|
|
18
29
|
alias :conjur_account :account
|
|
19
|
-
alias :conjur_account= :account=
|
|
20
|
-
|
|
21
|
-
def new_association(cls, params = {})
|
|
22
|
-
cls.new params.merge({userid: login})
|
|
23
|
-
end
|
|
30
|
+
# alias :conjur_account= :account=
|
|
24
31
|
|
|
25
32
|
# Returns the global privilege which was present on the request, if and only
|
|
26
33
|
# if the user actually has that privilege.
|
|
@@ -41,13 +48,21 @@ module Conjur
|
|
|
41
48
|
validated_global_privilege == "reveal"
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
# True if and only if the user has valid global '
|
|
45
|
-
def
|
|
46
|
-
validated_global_privilege == "
|
|
51
|
+
# True if and only if the user has valid global 'elevate' privilege.
|
|
52
|
+
def global_elevate?
|
|
53
|
+
validated_global_privilege == "elevate"
|
|
47
54
|
end
|
|
48
55
|
|
|
49
56
|
def login
|
|
50
|
-
|
|
57
|
+
parse_token
|
|
58
|
+
|
|
59
|
+
@login
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def attributes
|
|
63
|
+
parse_token
|
|
64
|
+
|
|
65
|
+
@attributes || {}
|
|
51
66
|
end
|
|
52
67
|
|
|
53
68
|
def roleid
|
|
@@ -63,17 +78,56 @@ module Conjur
|
|
|
63
78
|
def role
|
|
64
79
|
api.role(roleid)
|
|
65
80
|
end
|
|
66
|
-
|
|
81
|
+
|
|
82
|
+
def audit_resources
|
|
83
|
+
Conjur::API.decode_audit_ids(@audit_resources) if @audit_resources
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def audit_roles
|
|
87
|
+
Conjur::API.decode_audit_ids(@audit_roles) if @audit_roles
|
|
88
|
+
end
|
|
89
|
+
|
|
67
90
|
def api(cls = Conjur::API)
|
|
68
91
|
args = [ token ]
|
|
69
92
|
args.push remote_ip if remote_ip
|
|
70
93
|
api = cls.new_from_token(*args)
|
|
71
|
-
if privilege
|
|
72
|
-
|
|
94
|
+
api = api.with_privilege(privilege) if privilege
|
|
95
|
+
api = api.with_audit_resources(audit_resources) if audit_resources
|
|
96
|
+
api = api.with_audit_roles(audit_roles) if audit_roles
|
|
97
|
+
|
|
98
|
+
api
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
protected
|
|
102
|
+
|
|
103
|
+
def parse_token
|
|
104
|
+
return if @login
|
|
105
|
+
|
|
106
|
+
@token = Slosilo::JWT token
|
|
107
|
+
load_jwt token
|
|
108
|
+
rescue ArgumentError
|
|
109
|
+
if data = token['data']
|
|
110
|
+
return load_legacy data
|
|
73
111
|
else
|
|
74
|
-
|
|
112
|
+
raise "malformed token"
|
|
75
113
|
end
|
|
76
114
|
end
|
|
115
|
+
|
|
116
|
+
def load_legacy data
|
|
117
|
+
if data.is_a?(String)
|
|
118
|
+
@login = token['data']
|
|
119
|
+
elsif data.is_a?(Hash)
|
|
120
|
+
@attributes = token['data'].clone
|
|
121
|
+
@login = @attributes.delete('login') or raise "No 'login' field in token data"
|
|
122
|
+
else
|
|
123
|
+
raise "Expecting String or Hash token data, got #{data.class.name}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def load_jwt jwt
|
|
128
|
+
@attributes = jwt.claims.merge (jwt.header || {}) # just pass all the info
|
|
129
|
+
@login = jwt.claims['sub'] or raise "No 'sub' field in claims"
|
|
130
|
+
end
|
|
77
131
|
end
|
|
78
132
|
end
|
|
79
133
|
end
|
data/lib/conjur/rack/version.rb
CHANGED
|
@@ -3,107 +3,179 @@ require 'spec_helper'
|
|
|
3
3
|
require 'conjur/rack/authenticator'
|
|
4
4
|
|
|
5
5
|
describe Conjur::Rack::Authenticator do
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
'HTTP_AUTHORIZATION' => "Token token=\"#{basic_64}\""
|
|
17
|
-
}.tap do |e|
|
|
18
|
-
e['HTTP_X_CONJUR_PRIVILEGE'] = privilege if privilege
|
|
19
|
-
e['HTTP_X_FORWARDED_FOR'] = remote_ip if remote_ip
|
|
20
|
-
end
|
|
6
|
+
include_context "with authenticator"
|
|
7
|
+
|
|
8
|
+
describe "#call" do
|
|
9
|
+
context "to an unprotected path" do
|
|
10
|
+
let(:except) { [ /^\/foo/ ] }
|
|
11
|
+
let(:env) { { 'SCRIPT_NAME' => '', 'PATH_INFO' => '/foo/bar' } }
|
|
12
|
+
before {
|
|
13
|
+
options[:except] = except
|
|
14
|
+
expect(app).to receive(:call).with(env).and_return app
|
|
21
15
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
context "
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
it 'launches app' do
|
|
35
|
-
app.should_receive(:call).with(env).and_return app
|
|
36
|
-
call.should == app
|
|
16
|
+
context "without authorization" do
|
|
17
|
+
it "proceeds" do
|
|
18
|
+
expect(call).to eq(app)
|
|
19
|
+
expect(Conjur::Rack.identity?).to be(false)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
context "with authorization" do
|
|
23
|
+
include_context "with authorization"
|
|
24
|
+
it "ignores the authorization" do
|
|
25
|
+
expect(call).to eq(app)
|
|
26
|
+
expect(Conjur::Rack.identity?).to be(false)
|
|
37
27
|
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
it_should_behave_like 'returns User built from token'
|
|
31
|
+
context "to a protected path" do
|
|
32
|
+
let(:env) { { 'SCRIPT_NAME' => '/pathname' } }
|
|
33
|
+
context "without authorization" do
|
|
34
|
+
it "returns a 401 error" do
|
|
35
|
+
expect(call).to return_http 401, "Authorization missing"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
context "with Conjur authorization" do
|
|
39
|
+
include_context "with authorization"
|
|
40
|
+
|
|
41
|
+
context "with CIDR restriction" do
|
|
42
|
+
let(:claims) { { 'sub' => 'test-user', 'cidr' => %w(192.168.2.0/24 2001:db8::/32) } }
|
|
43
|
+
let(:token) { Slosilo::JWT.new(claims) }
|
|
44
|
+
before do
|
|
45
|
+
allow(subject).to receive_messages \
|
|
46
|
+
parsed_token: token,
|
|
47
|
+
http_remote_ip: remote_ip
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
%w(10.0.0.2 fdda:5cc1:23:4::1f).each do |addr|
|
|
51
|
+
context "with address #{addr} out of range" do
|
|
52
|
+
let(:remote_ip) { addr }
|
|
53
|
+
it "returns 403" do
|
|
54
|
+
expect(call).to return_http 403, "IP address rejected"
|
|
55
|
+
end
|
|
65
56
|
end
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
%w(192.168.2.3 2001:db8::22).each do |addr|
|
|
60
|
+
context "with address #{addr} in range" do
|
|
61
|
+
let(:remote_ip) { addr }
|
|
62
|
+
it "passes the request" do
|
|
63
|
+
expect(call.login).to eq 'test-user'
|
|
64
|
+
end
|
|
70
65
|
end
|
|
71
66
|
end
|
|
67
|
+
end
|
|
72
68
|
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
context "of a valid token" do
|
|
70
|
+
it 'launches app' do
|
|
71
|
+
expect(app).to receive(:call).with(env).and_return app
|
|
72
|
+
expect(call).to eq(app)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
context "of an invalid token" do
|
|
76
|
+
it "returns a 401 error" do
|
|
77
|
+
allow(Slosilo).to receive(:token_signer).and_return(nil)
|
|
78
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
context "of a token invalid for authn" do
|
|
82
|
+
it "returns a 401 error" do
|
|
83
|
+
allow(Slosilo).to receive(:token_signer).and_return('a-totally-different-key')
|
|
84
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
context "of 'own' token" do
|
|
88
|
+
it "returns ENV['CONJUR_ACCOUNT']" do
|
|
89
|
+
expect(ENV).to receive(:[]).with("CONJUR_ACCOUNT").and_return("test-account")
|
|
90
|
+
expect(app).to receive(:call) do |*args|
|
|
91
|
+
expect(Conjur::Rack.identity?).to be(true)
|
|
92
|
+
expect(Conjur::Rack.user.account).to eq('test-account')
|
|
93
|
+
:done
|
|
94
|
+
end
|
|
95
|
+
allow(Slosilo).to receive(:token_signer).and_return('own')
|
|
96
|
+
expect(call).to eq(:done)
|
|
97
|
+
end
|
|
98
|
+
it "requires ENV['CONJUR_ACCOUNT']" do
|
|
99
|
+
expect(ENV).to receive(:[]).with("CONJUR_ACCOUNT").and_return(nil)
|
|
100
|
+
allow(Slosilo).to receive(:token_signer).and_return('own')
|
|
101
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
|
75
102
|
end
|
|
76
103
|
end
|
|
77
104
|
end
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
105
|
+
|
|
106
|
+
context "with junk in token" do
|
|
107
|
+
let(:env) { { 'HTTP_AUTHORIZATION' => 'Token token="open sesame"' } }
|
|
108
|
+
it "returns 401" do
|
|
109
|
+
expect(call).to return_http 401, "Malformed authorization token"
|
|
82
110
|
end
|
|
83
111
|
end
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
|
|
113
|
+
context "with JSON junk in token" do
|
|
114
|
+
let(:env) { { 'HTTP_AUTHORIZATION' => 'Token token="eyJmb28iOiAiYmFyIn0="' } }
|
|
115
|
+
before do
|
|
116
|
+
allow(Slosilo).to receive(:token_signer).and_return(nil)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "returns 401" do
|
|
120
|
+
expect(call).to return_http 401, "Unauthorized: Invalid token"
|
|
88
121
|
end
|
|
89
122
|
end
|
|
90
123
|
end
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
context "to an optional path" do
|
|
125
|
+
let(:optional) { [ /^\/foo/ ] }
|
|
126
|
+
let(:env) { { 'SCRIPT_NAME' => '', 'PATH_INFO' => '/foo/bar' } }
|
|
127
|
+
before {
|
|
128
|
+
options[:optional] = optional
|
|
129
|
+
}
|
|
130
|
+
context "without authorization" do
|
|
131
|
+
it "proceeds" do
|
|
132
|
+
expect(app).to receive(:call) do |*args|
|
|
133
|
+
expect(Conjur::Rack.identity?).to be(false)
|
|
134
|
+
:done
|
|
135
|
+
end
|
|
136
|
+
expect(call).to eq(:done)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
context "with authorization" do
|
|
140
|
+
include_context "with authorization"
|
|
141
|
+
it "processes the authorization" do
|
|
142
|
+
expect(app).to receive(:call) do |*args|
|
|
143
|
+
expect(Conjur::Rack.identity?).to be(true)
|
|
144
|
+
:done
|
|
145
|
+
end
|
|
146
|
+
expect(call).to eq(:done)
|
|
147
|
+
end
|
|
97
148
|
end
|
|
98
149
|
end
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
150
|
+
|
|
151
|
+
RSpec::Matchers.define :return_http do |status, message|
|
|
152
|
+
match do |actual|
|
|
153
|
+
status, headers, body = actual
|
|
154
|
+
expect(status).to eq status
|
|
155
|
+
expect(headers).to eq "Content-Type" => "text/plain", "Content-Length" => message.length.to_s
|
|
156
|
+
expect(body.join).to eq message
|
|
106
157
|
end
|
|
107
158
|
end
|
|
108
159
|
end
|
|
160
|
+
|
|
161
|
+
# protected internal methods
|
|
162
|
+
|
|
163
|
+
describe '#verify_authorization_and_get_identity' do
|
|
164
|
+
it "accepts JWT tokens without CIDR restrictions" do
|
|
165
|
+
mock_jwt sub: 'user'
|
|
166
|
+
expect { subject.send :verify_authorization_and_get_identity }.to_not raise_error
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it "rejects JWT tokens with unrecognized claims" do
|
|
170
|
+
mock_jwt extra: 'field'
|
|
171
|
+
expect { subject.send :verify_authorization_and_get_identity }.to raise_error \
|
|
172
|
+
Conjur::Rack::Authenticator::AuthorizationError
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def mock_jwt claims
|
|
176
|
+
token = Slosilo::JWT.new(claims).add_signature(alg: 'none') {}
|
|
177
|
+
allow(subject).to receive(:parsed_token) { token }
|
|
178
|
+
allow(Slosilo).to receive(:token_signer).with(token).and_return 'authn:test'
|
|
179
|
+
end
|
|
180
|
+
end
|
|
109
181
|
end
|
|
@@ -17,21 +17,21 @@ describe Conjur::Rack::PathPrefix do
|
|
|
17
17
|
context "/api/hosts" do
|
|
18
18
|
let(:path) { "/api/hosts" }
|
|
19
19
|
it "matches" do
|
|
20
|
-
app.
|
|
20
|
+
expect(app).to receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
|
|
21
21
|
call
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
context "/api" do
|
|
25
25
|
let(:path) { "/api" }
|
|
26
26
|
it "doesn't erase the path completely" do
|
|
27
|
-
app.
|
|
27
|
+
expect(app).to receive(:call).with({ 'PATH_INFO' => '/' }).and_return app
|
|
28
28
|
call
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
context "with non-matching prefix" do
|
|
32
32
|
let(:path) { "/hosts" }
|
|
33
33
|
it "doesn't match" do
|
|
34
|
-
app.
|
|
34
|
+
expect(app).to receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
|
|
35
35
|
call
|
|
36
36
|
end
|
|
37
37
|
end
|
data/spec/rack/user_spec.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
+
require 'conjur/rack/user'
|
|
2
3
|
|
|
3
4
|
describe Conjur::Rack::User do
|
|
4
5
|
let(:login){ 'admin' }
|
|
@@ -6,41 +7,50 @@ describe Conjur::Rack::User do
|
|
|
6
7
|
let(:account){ 'acct' }
|
|
7
8
|
let(:privilege) { nil }
|
|
8
9
|
let(:remote_ip) { nil }
|
|
10
|
+
let(:audit_roles) { nil }
|
|
11
|
+
let(:audit_resources) { nil }
|
|
9
12
|
|
|
10
|
-
subject{
|
|
13
|
+
subject(:user) {
|
|
14
|
+
described_class.new(token, account,
|
|
15
|
+
:privilege => privilege,
|
|
16
|
+
:remote_ip => remote_ip,
|
|
17
|
+
:audit_roles => audit_roles,
|
|
18
|
+
:audit_resources => audit_resources
|
|
19
|
+
)
|
|
20
|
+
}
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
it "aliases setter for account to conjur_account" do
|
|
18
|
-
subject.conjur_account = "changed!"
|
|
19
|
-
subject.account.should == "changed!"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
describe '#new_assocation' do
|
|
23
|
-
let(:associate){ Class.new }
|
|
24
|
-
let(:params){{foo: 'bar'}}
|
|
25
|
-
it "calls cls.new with params including userid: login" do
|
|
26
|
-
associate.should_receive(:new).with(params.merge(userid: subject.login))
|
|
27
|
-
subject.new_association(associate, params)
|
|
28
|
-
end
|
|
22
|
+
it 'provides field accessors' do
|
|
23
|
+
expect(user.token).to eq token
|
|
24
|
+
expect(user.account).to eq account
|
|
25
|
+
expect(user.conjur_account).to eq account
|
|
26
|
+
expect(user.login).to eq login
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
describe '#roleid' do
|
|
32
30
|
let(:login){ tokens.join('/') }
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
|
|
32
|
+
context "when login contains one token" do
|
|
33
|
+
let(:tokens) { %w(foobar) }
|
|
34
|
+
|
|
35
|
+
it "is expanded to account:user:token" do
|
|
36
|
+
expect(subject.roleid).to eq "#{account}:user:foobar"
|
|
37
|
+
end
|
|
36
38
|
end
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
context "when login contains two tokens" do
|
|
41
|
+
let(:tokens) { %w(foo bar) }
|
|
42
|
+
|
|
43
|
+
it "is expanded to account:first:second" do
|
|
44
|
+
expect(subject.roleid).to eq "#{account}:foo:bar"
|
|
45
|
+
end
|
|
40
46
|
end
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
|
|
48
|
+
context "when login contains three tokens" do
|
|
49
|
+
let(:tokens) { %w(foo bar baz) }
|
|
50
|
+
|
|
51
|
+
it "is expanded to account:first:second/third" do
|
|
52
|
+
expect(subject.roleid).to eq "#{account}:foo:bar/baz"
|
|
53
|
+
end
|
|
44
54
|
end
|
|
45
55
|
end
|
|
46
56
|
|
|
@@ -48,13 +58,13 @@ describe Conjur::Rack::User do
|
|
|
48
58
|
let(:roleid){ 'the role id' }
|
|
49
59
|
let(:api){ double('conjur api') }
|
|
50
60
|
before do
|
|
51
|
-
subject.
|
|
52
|
-
subject.
|
|
61
|
+
allow(subject).to receive(:roleid).and_return roleid
|
|
62
|
+
allow(subject).to receive(:api).and_return api
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
it 'passes roleid to api.role' do
|
|
56
|
-
api.
|
|
57
|
-
subject.role.
|
|
66
|
+
expect(api).to receive(:role).with(roleid).and_return 'the role'
|
|
67
|
+
expect(subject.role).to eq('the role')
|
|
58
68
|
end
|
|
59
69
|
end
|
|
60
70
|
|
|
@@ -63,19 +73,19 @@ describe Conjur::Rack::User do
|
|
|
63
73
|
let(:privilege) { "reveal" }
|
|
64
74
|
let(:api){ Conjur::API.new_from_token "the-token" }
|
|
65
75
|
before do
|
|
66
|
-
subject.
|
|
76
|
+
allow(subject).to receive(:api).and_return(api)
|
|
67
77
|
end
|
|
68
78
|
it "checks the API function global_privilege_permitted?" do
|
|
69
|
-
api.
|
|
70
|
-
resource.
|
|
71
|
-
expect(subject.global_reveal?).to
|
|
79
|
+
expect(api).to receive(:resource).with("!:!:conjur").and_return(resource = double(:resource))
|
|
80
|
+
expect(resource).to receive(:permitted?).with("reveal").and_return(true)
|
|
81
|
+
expect(subject.global_reveal?).to be true
|
|
72
82
|
# The result is cached
|
|
73
83
|
subject.global_reveal?
|
|
74
84
|
end
|
|
75
85
|
end
|
|
76
86
|
context "without a global privilege" do
|
|
77
87
|
it "simply returns nil" do
|
|
78
|
-
expect(subject.global_reveal?).to
|
|
88
|
+
expect(subject.global_reveal?).to be false
|
|
79
89
|
end
|
|
80
90
|
end
|
|
81
91
|
end
|
|
@@ -84,38 +94,96 @@ describe Conjur::Rack::User do
|
|
|
84
94
|
context "when given a class" do
|
|
85
95
|
let(:cls){ double('API class') }
|
|
86
96
|
it "calls cls.new_from_token with its token" do
|
|
87
|
-
cls.
|
|
88
|
-
subject.api(cls).
|
|
97
|
+
expect(cls).to receive(:new_from_token).with(token).and_return 'the api'
|
|
98
|
+
expect(subject.api(cls)).to eq('the api')
|
|
89
99
|
end
|
|
90
100
|
end
|
|
91
101
|
context 'when not given args' do
|
|
92
102
|
shared_examples_for "builds the api" do
|
|
93
|
-
|
|
94
|
-
subject.api.should == 'the api'
|
|
95
|
-
}
|
|
103
|
+
its(:api) { should == 'the api' }
|
|
96
104
|
end
|
|
97
105
|
|
|
98
106
|
context "with no extra args" do
|
|
99
107
|
before {
|
|
100
|
-
Conjur::API.
|
|
108
|
+
expect(Conjur::API).to receive(:new_from_token).with(token).and_return('the api')
|
|
101
109
|
}
|
|
102
110
|
it_should_behave_like "builds the api"
|
|
103
111
|
end
|
|
104
112
|
context "with remote_ip" do
|
|
105
113
|
let(:remote_ip) { "the-ip" }
|
|
106
114
|
before {
|
|
107
|
-
Conjur::API.
|
|
115
|
+
expect(Conjur::API).to receive(:new_from_token).with(token, 'the-ip').and_return('the api')
|
|
108
116
|
}
|
|
109
117
|
it_should_behave_like "builds the api"
|
|
110
118
|
end
|
|
111
119
|
context "with privilege" do
|
|
112
|
-
let(:privilege) { "
|
|
120
|
+
let(:privilege) { "elevate" }
|
|
121
|
+
before {
|
|
122
|
+
expect(Conjur::API).to receive(:new_from_token).with(token).and_return(api = double(:api))
|
|
123
|
+
expect(api).to receive(:with_privilege).with("elevate").and_return('the api')
|
|
124
|
+
}
|
|
125
|
+
it_should_behave_like "builds the api"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "with audit resource" do
|
|
129
|
+
let (:audit_resources) { 'food:bacon' }
|
|
113
130
|
before {
|
|
114
|
-
Conjur::API.
|
|
115
|
-
expect(api).to receive(:
|
|
131
|
+
expect(Conjur::API).to receive(:new_from_token).with(token).and_return(api = double(:api))
|
|
132
|
+
expect(api).to receive(:with_audit_resources).with(['food:bacon']).and_return('the api')
|
|
116
133
|
}
|
|
117
134
|
it_should_behave_like "builds the api"
|
|
118
135
|
end
|
|
136
|
+
|
|
137
|
+
context "with audit roles" do
|
|
138
|
+
let (:audit_roles) { 'user:cook' }
|
|
139
|
+
before {
|
|
140
|
+
expect(Conjur::API).to receive(:new_from_token).with(token).and_return(api = double(:api))
|
|
141
|
+
expect(api).to receive(:with_audit_roles).with(['user:cook']).and_return('the api')
|
|
142
|
+
}
|
|
143
|
+
it_should_behave_like "builds the api"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context "with invalid type payload" do
|
|
150
|
+
let(:token){ { "data" => :alice } }
|
|
151
|
+
it "raises an error on trying to access the content" do
|
|
152
|
+
expect{ subject.login }.to raise_error("Expecting String or Hash token data, got Symbol")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context "with hash payload" do
|
|
157
|
+
let(:token){ { "data" => { "login" => "alice", "capabilities" => { "fry" => "bacon" } } } }
|
|
158
|
+
|
|
159
|
+
it "processes the login and attributes" do
|
|
160
|
+
original_token = token.deep_dup
|
|
161
|
+
|
|
162
|
+
expect(subject.login).to eq('alice')
|
|
163
|
+
expect(subject.attributes).to eq({"capabilities" => { "fry" => "bacon" }})
|
|
164
|
+
|
|
165
|
+
expect(token).to eq original_token
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
context "with JWT token" do
|
|
170
|
+
let(:token) { {"protected"=>"eyJhbGciOiJ0ZXN0IiwidHlwIjoiSldUIn0=",
|
|
171
|
+
"payload"=>"eyJzdWIiOiJhbGljZSIsImlhdCI6MTUwNDU1NDI2NX0=",
|
|
172
|
+
"signature"=>"dGVzdHNpZw=="} }
|
|
173
|
+
|
|
174
|
+
it "processes the login and attributes" do
|
|
175
|
+
original_token = token.deep_dup
|
|
176
|
+
|
|
177
|
+
expect(subject.login).to eq('alice')
|
|
178
|
+
|
|
179
|
+
# TODO: should we only pass unrecognized attrs here?
|
|
180
|
+
expect(subject.attributes).to eq \
|
|
181
|
+
'alg' => 'test',
|
|
182
|
+
'iat' => 1504554265,
|
|
183
|
+
'sub' => 'alice',
|
|
184
|
+
'typ' => 'JWT'
|
|
185
|
+
|
|
186
|
+
expect(token).to eq original_token
|
|
119
187
|
end
|
|
120
188
|
end
|
|
121
|
-
end
|
|
189
|
+
end
|
data/spec/rack_spec.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'conjur/rack'
|
|
3
|
+
|
|
4
|
+
describe Conjur::Rack do
|
|
5
|
+
describe '.user' do
|
|
6
|
+
include_context "with authorization"
|
|
7
|
+
let(:stubuser) { double :stubuser }
|
|
8
|
+
before do
|
|
9
|
+
allow(Conjur::Rack::User).to receive(:new)
|
|
10
|
+
.with(token, 'someacc', {:privilege => privilege, :remote_ip => remote_ip, :audit_roles => audit_roles, :audit_resources => audit_resources})
|
|
11
|
+
.and_return(stubuser)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context 'when called in app context' do
|
|
15
|
+
shared_examples_for :returns_user do
|
|
16
|
+
it "returns user built from token" do
|
|
17
|
+
expect(call).to eq stubuser
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
include_examples :returns_user
|
|
22
|
+
|
|
23
|
+
context 'with X-Conjur-Privilege' do
|
|
24
|
+
let(:privilege) { "elevate" }
|
|
25
|
+
include_examples :returns_user
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'with X-Forwarded-For' do
|
|
29
|
+
let(:remote_ip) { "66.0.0.1" }
|
|
30
|
+
include_examples :returns_user
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with Conjur-Audit-Roles' do
|
|
34
|
+
let (:audit_roles) { 'user%3Acook' }
|
|
35
|
+
include_examples :returns_user
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'with Conjur-Audit-Resources' do
|
|
39
|
+
let (:audit_resources) { 'food%3Abacon' }
|
|
40
|
+
include_examples :returns_user
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "raises error if called out of app context" do
|
|
46
|
+
expect { Conjur::Rack.user }.to raise_error('No Conjur identity for current request')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -4,8 +4,44 @@ $:.unshift File.join(File.dirname(__FILE__), "lib")
|
|
|
4
4
|
|
|
5
5
|
# Allows loading of an environment config based on the environment
|
|
6
6
|
require 'rspec'
|
|
7
|
+
require 'rspec/its'
|
|
7
8
|
require 'securerandom'
|
|
9
|
+
require 'slosilo'
|
|
8
10
|
|
|
9
11
|
RSpec.configure do |config|
|
|
10
12
|
end
|
|
11
13
|
|
|
14
|
+
RSpec.shared_context "with authenticator" do
|
|
15
|
+
let(:options) { {} }
|
|
16
|
+
let(:app) { double(:app) }
|
|
17
|
+
subject(:authenticator) { Conjur::Rack::Authenticator.new(app, options) }
|
|
18
|
+
let(:call) { authenticator.call env }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
RSpec.shared_context "with authorization" do
|
|
22
|
+
include_context "with authenticator"
|
|
23
|
+
let(:token_signer) { "authn:someacc" }
|
|
24
|
+
let(:audit_resources) { nil }
|
|
25
|
+
let(:privilege) { nil }
|
|
26
|
+
let(:remote_ip) { nil }
|
|
27
|
+
let(:audit_roles) { nil }
|
|
28
|
+
|
|
29
|
+
before do
|
|
30
|
+
allow(app).to receive(:call) { Conjur::Rack.user }
|
|
31
|
+
allow(Slosilo).to receive(:token_signer).and_return(token_signer)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
let(:env) do
|
|
35
|
+
{
|
|
36
|
+
'HTTP_AUTHORIZATION' => "Token token=\"#{basic_64}\""
|
|
37
|
+
}.tap do |e|
|
|
38
|
+
e['HTTP_X_CONJUR_PRIVILEGE'] = privilege if privilege
|
|
39
|
+
e['HTTP_X_FORWARDED_FOR'] = remote_ip if remote_ip
|
|
40
|
+
e['HTTP_CONJUR_AUDIT_ROLES'] = audit_roles if audit_roles
|
|
41
|
+
e['HTTP_CONJUR_AUDIT_RESOURCES'] = audit_resources if audit_resources
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let(:basic_64) { Base64.strict_encode64(token.to_json) }
|
|
46
|
+
let(:token) { { "data" => "foobar" } }
|
|
47
|
+
end
|
metadata
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: conjur-rack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kevin Gilpin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2017-10-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: slosilo
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '2.1'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '2.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: conjur-api
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - "<"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '6'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - "<"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '6'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rack
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '1'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '1'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: bundler
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,22 +86,44 @@ dependencies:
|
|
|
86
86
|
requirements:
|
|
87
87
|
- - ">="
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
90
|
-
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
91
95
|
- !ruby/object:Gem::Version
|
|
92
|
-
version: '
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: ci_reporter_rspec
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
93
104
|
type: :development
|
|
94
105
|
prerelease: false
|
|
95
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
96
107
|
requirements:
|
|
97
108
|
- - ">="
|
|
98
109
|
- !ruby/object:Gem::Version
|
|
99
|
-
version: '
|
|
100
|
-
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: pry-byebug
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
101
116
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
103
125
|
- !ruby/object:Gem::Dependency
|
|
104
|
-
name:
|
|
126
|
+
name: rspec-its
|
|
105
127
|
requirement: !ruby/object:Gem::Requirement
|
|
106
128
|
requirements:
|
|
107
129
|
- - ">="
|
|
@@ -123,13 +145,13 @@ extra_rdoc_files: []
|
|
|
123
145
|
files:
|
|
124
146
|
- ".gitignore"
|
|
125
147
|
- ".project"
|
|
126
|
-
- ".rvmrc"
|
|
127
148
|
- CHANGELOG.md
|
|
128
149
|
- Gemfile
|
|
129
150
|
- LICENSE.txt
|
|
130
151
|
- README.md
|
|
131
152
|
- Rakefile
|
|
132
153
|
- conjur-rack.gemspec
|
|
154
|
+
- jenkins.sh
|
|
133
155
|
- lib/conjur/rack.rb
|
|
134
156
|
- lib/conjur/rack/authenticator.rb
|
|
135
157
|
- lib/conjur/rack/path_prefix.rb
|
|
@@ -138,6 +160,7 @@ files:
|
|
|
138
160
|
- spec/rack/authenticator_spec.rb
|
|
139
161
|
- spec/rack/path_prefix_spec.rb
|
|
140
162
|
- spec/rack/user_spec.rb
|
|
163
|
+
- spec/rack_spec.rb
|
|
141
164
|
- spec/spec_helper.rb
|
|
142
165
|
homepage: http://github.com/conjurinc/conjur-rack
|
|
143
166
|
licenses:
|
|
@@ -159,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
159
182
|
version: '0'
|
|
160
183
|
requirements: []
|
|
161
184
|
rubyforge_project:
|
|
162
|
-
rubygems_version: 2.
|
|
185
|
+
rubygems_version: 2.6.13
|
|
163
186
|
signing_key:
|
|
164
187
|
specification_version: 4
|
|
165
188
|
summary: Rack authenticator and basic User struct
|
|
@@ -167,4 +190,5 @@ test_files:
|
|
|
167
190
|
- spec/rack/authenticator_spec.rb
|
|
168
191
|
- spec/rack/path_prefix_spec.rb
|
|
169
192
|
- spec/rack/user_spec.rb
|
|
193
|
+
- spec/rack_spec.rb
|
|
170
194
|
- spec/spec_helper.rb
|
data/.rvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
rvm use --create 2.0.0@conjur-rack
|