Skip to content

Commit

Permalink
Apply CIDR restrictions
Browse files Browse the repository at this point in the history
Closes #11.
  • Loading branch information
dividedmind committed Sep 19, 2017
1 parent 244f412 commit b33153d
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 23 deletions.
23 changes: 17 additions & 6 deletions lib/conjur/rack/authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class AuthorizationError < SecurityError
end
class SignatureError < SecurityError
end
class Forbidden < SecurityError
end

attr_reader :app, :options

Expand Down Expand Up @@ -77,6 +79,8 @@ def call rackenv
conjur_rack[:audit_resources] = http_audit_resources
end

rescue Forbidden
return error 403, $!.message
rescue SecurityError, RestClient::Exception
return error 401, $!.message
end
Expand Down Expand Up @@ -109,16 +113,23 @@ def validate_token_and_get_account token
def error status, message
[status, { 'Content-Type' => 'text/plain', 'Content-Length' => message.length.to_s }, [message] ]
end

def parsed_token
token = http_authorization.to_s[/^Token token="(.*)"/, 1]
token = token && JSON.parse(Base64.decode64(token))
token = Slosilo::JWT token rescue token
rescue JSON::ParserError
raise AuthorizationError.new("Malformed authorization token")
end

def verify_authorization_and_get_identity
if http_authorization.to_s[/^Token token="(.*)"/]
if token = parsed_token
begin
token = JSON.parse(Base64.decode64($1))
account = validate_token_and_get_account(token)
# TODO check CIDR restrictions
account = validate_token_and_get_account token
if token.respond_to?(:claims) && (cidr = token.claims['cidr'].map(&IPAddr.method(:new)))
raise Forbidden, "IP address rejected" unless cidr.any? { |c| c.include? http_remote_ip }
end
return [token, account]
rescue JSON::ParserError
raise AuthorizationError.new("Malformed authorization token")
end
else
path = http_path
Expand Down
14 changes: 5 additions & 9 deletions lib/conjur/rack/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,14 @@ def api(cls = Conjur::API)
def parse_token
return if @login

@token = Slosilo::JWT token
load_jwt token
rescue ArgumentError
if data = token['data']
return load_legacy data
else
begin
@token = Slosilo::JWT token
return load_jwt token
rescue ArgumentError
# pass
end
raise "malformed token"
end

raise "malformed token"
end

def load_legacy data
Expand All @@ -129,7 +125,7 @@ def load_legacy data
end

def load_jwt jwt
@attributes = jwt.claims.merge jwt.header # just pass all the info
@attributes = jwt.claims.merge (jwt.header || {}) # just pass all the info
@login = jwt.claims['sub'] or raise "No 'sub' field in claims"
end
end
Expand Down
49 changes: 42 additions & 7 deletions spec/rack/authenticator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,39 @@
let(:env) { { 'SCRIPT_NAME' => '/pathname' } }
context "without authorization" do
it "returns a 401 error" do
expect(call).to eq([401, {"Content-Type"=>"text/plain", "Content-Length"=>"21"}, ["Authorization missing"]])
expect(call).to return_http 401, "Authorization missing"
end
end
context "with Conjur authorization" do
include_context "with authorization"

it "checks CIDR restrictions"
context "with CIDR restriction" do
let(:claims) { { 'sub' => 'test-user', 'cidr' => %w(192.168.2.0/24 2001:db8::/32) } }
let(:token) { Slosilo::JWT.new(claims) }
before do
allow(subject).to receive_messages \
parsed_token: token,
http_remote_ip: remote_ip
end

%w(10.0.0.2 fdda:5cc1:23:4::1f).each do |addr|
context "with address #{addr} out of range" do
let(:remote_ip) { addr }
it "returns 403" do
expect(call).to return_http 403, "IP address rejected"
end
end
end

%w(192.168.2.3 2001:db8::22).each do |addr|
context "with address #{addr} in range" do
let(:remote_ip) { addr }
it "passes the request" do
expect(call.login).to eq 'test-user'
end
end
end
end

context "of a valid token" do
it 'launches app' do
Expand All @@ -48,13 +74,13 @@
context "of an invalid token" do
it "returns a 401 error" do
allow(Slosilo).to receive(:token_signer).and_return(nil)
expect(call).to eq([401, {"Content-Type"=>"text/plain", "Content-Length"=>"27"}, ["Unauthorized: Invalid token"]])
expect(call).to return_http 401, "Unauthorized: Invalid token"
end
end
context "of a token invalid for authn" do
it "returns a 401 error" do
allow(Slosilo).to receive(:token_signer).and_return('a-totally-different-key')
expect(call).to eq([401, {"Content-Type"=>"text/plain", "Content-Length"=>"27"}, ["Unauthorized: Invalid token"]])
expect(call).to return_http 401, "Unauthorized: Invalid token"
end
end
context "of 'own' token" do
Expand All @@ -71,15 +97,15 @@
it "requires ENV['CONJUR_ACCOUNT']" do
expect(ENV).to receive(:[]).with("CONJUR_ACCOUNT").and_return(nil)
allow(Slosilo).to receive(:token_signer).and_return('own')
expect(call).to eq([401, {"Content-Type"=>"text/plain", "Content-Length"=>"27"}, ["Unauthorized: Invalid token"]])
expect(call).to return_http 401, "Unauthorized: Invalid token"
end
end
end

context "with junk in token" do
let(:env) { { 'HTTP_AUTHORIZATION' => 'Token token="open sesame"' } }
it "returns 401" do
expect(call).to eq([401, {"Content-Type"=>"text/plain", "Content-Length"=>"29"}, ["Malformed authorization token"]])
expect(call).to return_http 401, "Malformed authorization token"
end
end

Expand All @@ -90,7 +116,7 @@
end

it "returns 401" do
expect(call).to eq([401, {"Content-Type"=>"text/plain", "Content-Length"=>"27"}, ["Unauthorized: Invalid token"]])
expect(call).to return_http 401, "Unauthorized: Invalid token"
end
end
end
Expand Down Expand Up @@ -120,5 +146,14 @@
end
end
end

RSpec::Matchers.define :return_http do |status, message|
match do |actual|
status, headers, body = actual
expect(status).to eq status
expect(headers).to eq "Content-Type" => "text/plain", "Content-Length" => message.length.to_s
expect(body.join).to eq message
end
end
end
end
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
RSpec.shared_context "with authenticator" do
let(:options) { {} }
let(:app) { double(:app) }
let(:authenticator) { Conjur::Rack::Authenticator.new(app, options) }
subject(:authenticator) { Conjur::Rack::Authenticator.new(app, options) }
let(:call) { authenticator.call env }
end

Expand Down

0 comments on commit b33153d

Please sign in to comment.