conjur-rack 1.4.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90340dd3a8dbc2f410a43b66ef10d010e76a9ffd
4
- data.tar.gz: 9cc5f13b006ac102df5a28b65da08e7f3a7ef6dd
3
+ metadata.gz: 0ee85c177c59da73e229825170b3b8c34230e4e1
4
+ data.tar.gz: 7758e75373c0ccdfcb61539a33325795d5394600
5
5
  SHA512:
6
- metadata.gz: 9223bfe5621080f8b9dba63e2de5593e2f6e44d2ad34d3beb60c8447a0ff8340eae3d1530420975052cfcbe6d26a66e2bbc5c8a1737f0019a2ee437c0fe458a3
7
- data.tar.gz: 58042ba2ee61e535da72b1395ab7f48e1a873d8fba78fdc0ac1ab1a01a15b3dda63e7ba78fe83a550876c180238fbe5d38917d50f5c0485cf8dba1f42bfdbb67
6
+ metadata.gz: ccec8cadd0827fbf5b298f57fa78aa32a7792a58d985c0ff54ee05b80c256e55c0a9c868a1432be4908d7314c9becefdcf812ed95aad28ec756e989ad250def2
7
+ data.tar.gz: 36e507e1714480f2c5e4f5ff9d3af63cca883f855fc2259e272c3ce45420dadc36e0cd64cbe926f6396d0d3793968f802df147950ac0db86c7b69bba3abd0b8d
@@ -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: 'conjurinc/api-ruby', branch: 'master'
12
+ # gem 'conjur-api', github: 'cyberark/conjur-api-ruby', branch: 'master'
@@ -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", ">= 4.17"
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", ">=2.9", "<3.0"
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
@@ -0,0 +1,12 @@
1
+ #!/bin/bash -e
2
+
3
+ TEST_IMAGE='ruby:2.3.4'
4
+
5
+ rm -f Gemfile.lock
6
+
7
+ docker run --rm \
8
+ -v "$PWD:/usr/src/app" \
9
+ -w /usr/src/app \
10
+ -e CONJUR_ENV=ci \
11
+ $TEST_IMAGE \
12
+ bash -c "bundle update && bundle exec rake spec"
@@ -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
- def self.identity?
6
- !Thread.current[:conjur_rack_identity].nil?
7
- end
8
-
9
- def self.user
10
- User.new(identity[0], identity[1], privilege, remote_ip)
11
- end
12
-
13
- def self.identity
14
- Thread.current[:conjur_rack_identity] or raise "No Conjur identity for current request"
15
- end
16
-
17
- def self.privilege
18
- Thread.current[:conjur_rack_privilege]
19
- end
20
-
21
- def self.remote_ip
22
- Thread.current[:conjur_rack_remote_ip]
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
- def token; Thread.current[:conjur_rack_token] ; end
43
- def account; Thread.current[:conjur_rack_account]; end
44
- def privilege; Thread.current[:conjur_rack_privilege]; end
45
- def remote_ip; Thread.current[:conjur_rack_remote_ip]; end
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
- Thread.current[:conjur_rack_token] = identity[0]
55
- Thread.current[:conjur_rack_account] = identity[1]
56
- Thread.current[:conjur_rack_identity] = identity
57
- Thread.current[:conjur_rack_privilege] = conjur_privilege
58
- Thread.current[:conjur_rack_remote_ip] = remote_ip
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[:conjur_rack_identity] = nil
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("Unathorized: Invalid token")
103
+ failure = SignatureError.new("Unauthorized: Invalid token")
80
104
  raise failure unless (signer = Slosilo.token_signer token)
81
- raise failure unless signer =~ /\Aauthn:(.+)\z/
82
- return $1
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 authorization.to_s[/^Token token="(.*)"/]
91
- token = JSON.parse(Base64.decode64($1))
92
- account = validate_token_and_get_account(token)
93
- return [token, account]
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
- raise AuthorizationError.new("Authorization missing")
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 authorization
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 remote_ip
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 path
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
@@ -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
- attr_accessor :token, :account, :privilege, :remote_ip
9
+ attr_reader :token, :account, :privilege, :remote_ip, :audit_roles, :audit_resources
7
10
 
8
- def initialize(token, account, privilege = nil, remote_ip = nil)
11
+ def initialize(token, account, options = {})
9
12
  @token = token
10
13
  @account = account
11
- @privilege = privilege
12
- @remote_ip = remote_ip
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 'sudo' privilege.
45
- def global_sudo?
46
- validated_global_privilege == "sudo"
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
- token["data"] or raise "No data field in token"
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
- api.with_privilege(privilege)
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
- api
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
@@ -1,5 +1,5 @@
1
1
  module Conjur
2
2
  module Rack
3
- VERSION = "1.4.0"
3
+ VERSION = "3.1.0"
4
4
  end
5
5
  end
@@ -3,107 +3,179 @@ require 'spec_helper'
3
3
  require 'conjur/rack/authenticator'
4
4
 
5
5
  describe Conjur::Rack::Authenticator do
6
- let(:app) { double(:app) }
7
- let(:options) { {} }
8
- let(:authenticator) { Conjur::Rack::Authenticator.new(app, options) }
9
- let(:call) { authenticator.call env }
10
-
11
- context "#call" do
12
- context "with Conjur authorization" do
13
- before{ stub_const 'Slosilo', Module.new }
14
- let(:env) {
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
- let(:basic_64) { Base64.strict_encode64(token.to_json) }
23
- let(:token) { { "data" => "foobar" } }
24
- let(:sample_account) { "someacc" }
25
- let(:privilege) { nil }
26
- let(:remote_ip) { nil }
27
-
28
- context "of a valid token" do
29
-
30
- before(:each) {
31
- Slosilo.stub token_signer: 'authn:'+sample_account
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
- context 'Authable provides module method conjur_user' do
40
- let(:stubuser) { "some value" }
41
- before {
42
- app.stub(:call) { Conjur::Rack.user }
43
- }
44
-
45
- context 'when called in app context' do
46
- let(:invoke) {
47
- Conjur::Rack::User.should_receive(:new).
48
- with(token, sample_account, privilege, remote_ip).
49
- and_return(stubuser)
50
- Conjur::Rack.should_receive(:user).and_call_original
51
- call
52
- }
53
-
54
- shared_examples_for 'returns User built from token' do
55
- specify {
56
- invoke.should == stubuser
57
- }
58
- end
59
-
60
- it_should_behave_like 'returns User built from token'
61
-
62
- context 'with X-Conjur-Privilege' do
63
- let(:privilege) { "sudo" }
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
- context 'with X-Forwarded-For' do
68
- let(:remote_ip) { "66.0.0.1" }
69
- it_should_behave_like 'returns User built from token'
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
- context 'called out of app context' do
74
- it { lambda { Conjur::Rack.user }.should raise_error }
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
- context "of an invalid token" do
79
- it "returns a 401 error" do
80
- Slosilo.stub token_signer: nil
81
- call.should == [401, {"Content-Type"=>"text/plain", "Content-Length"=>"26"}, ["Unathorized: Invalid token"]]
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
- context "of a token invalid for authn" do
85
- it "returns a 401 error" do
86
- Slosilo.stub token_signer: 'a-totally-different-key'
87
- call.should == [401, {"Content-Type"=>"text/plain", "Content-Length"=>"26"}, ["Unathorized: Invalid token"]]
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
- end
92
- context "without authorization" do
93
- context "to a protected path" do
94
- let(:env) { { 'SCRIPT_NAME' => '/pathname' } }
95
- it "returns a 401 error" do
96
- call.should == [401, {"Content-Type"=>"text/plain", "Content-Length"=>"21"}, ["Authorization missing"]]
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
- context "to an unprotected path" do
100
- let(:except) { [ /^\/foo/ ] }
101
- let(:env) { { 'SCRIPT_NAME' => '', 'PATH_INFO' => '/foo/bar' } }
102
- it "proceeds" do
103
- options[:except] = except
104
- app.should_receive(:call).with(env).and_return app
105
- call.should == app
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.should_receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return 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.should_receive(:call).with({ 'PATH_INFO' => '/' }).and_return 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.should_receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
34
+ expect(app).to receive(:call).with({ 'PATH_INFO' => '/hosts' }).and_return app
35
35
  call
36
36
  end
37
37
  end
@@ -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{ described_class.new token, account, privilege, remote_ip }
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
- its(:token){ should == token }
13
- its(:account){ should == account }
14
- its(:conjur_account){ should == account }
15
- its(:login){ should == token['data'] }
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
- context "when login contains one token 'foobar'" do
34
- let(:tokens){ ['foobar'] }
35
- its(:roleid){ should == "#{account}:user:#{login}" }
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
- context "when login contains tokens ['foo', 'bar']" do
38
- let(:tokens){ ["foos", "bar"] }
39
- its(:roleid){ should == "#{account}:#{tokens[0]}:#{tokens[1]}"}
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
- context "when login contains tokens ['foo','bar','baz']" do
42
- let(:tokens){ ['foo', 'bar', 'baz'] }
43
- its(:roleid){ should == "#{account}:#{tokens[0]}:#{tokens[1]}/#{tokens[2]}" }
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.stub(:roleid).and_return roleid
52
- subject.stub(:api).and_return api
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.should_receive(:role).with(roleid).and_return 'the role'
57
- subject.role.should == 'the 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.stub(:api).and_return api
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.should_receive(:resource).with("!:!:conjur").and_return resource = double(:resource)
70
- resource.should_receive(:permitted?).with("reveal").and_return true
71
- expect(subject.global_reveal?).to be_true
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 be_false
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.should_receive(:new_from_token).with(token).and_return 'the api'
88
- subject.api(cls).should == 'the api'
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
- specify {
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.should_receive(:new_from_token).with(token).and_return 'the 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.should_receive(:new_from_token).with(token, 'the-ip').and_return 'the 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) { "sudo" }
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.should_receive(:new_from_token).with(token).and_return api = double(:api)
115
- expect(api).to receive(:with_privilege).with("sudo").and_return('the api')
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
@@ -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
@@ -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.0
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: 2015-10-20 00:00:00.000000000 Z
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: '0'
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: '0'
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: '4.17'
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: '4.17'
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: '0'
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: '0'
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: '2.9'
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: '3.0'
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: '2.9'
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: '3.0'
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: ci_reporter_rspec
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.4.8
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