Commit fc8c1a77 authored by Tiger's avatar Tiger

Validate session key when authorizing with GCP to create a cluster

It was previously possible to link a GCP account to another
user's GitLab account by having them visit the callback URL,
as there was no check that they were the initiator of the
request.

We now reject the callback unless the state parameter
matches the one added to the initiating user's session.
parent 0f20c401
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
module GoogleApi module GoogleApi
class AuthorizationsController < ApplicationController class AuthorizationsController < ApplicationController
include Gitlab::Utils::StrongMemoize
before_action :validate_session_key!
def callback def callback
token, expires_at = GoogleApi::CloudPlatform::Client token, expires_at = GoogleApi::CloudPlatform::Client
.new(nil, callback_google_api_auth_url) .new(nil, callback_google_api_auth_url)
...@@ -11,21 +15,27 @@ module GoogleApi ...@@ -11,21 +15,27 @@ module GoogleApi
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
expires_at.to_s expires_at.to_s
state_redirect_uri = redirect_uri_from_session_key(params[:state]) redirect_to redirect_uri_from_session
end
private
def validate_session_key!
access_denied! unless redirect_uri_from_session.present?
end
if state_redirect_uri def redirect_uri_from_session
redirect_to state_redirect_uri strong_memoize(:redirect_uri_from_session) do
if params[:state].present?
session[session_key_for_redirect_uri(params[:state])]
else else
redirect_to root_path nil
end
end end
end end
private def session_key_for_redirect_uri(state)
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state)
def redirect_uri_from_session_key(state)
key = GoogleApi::CloudPlatform::Client
.session_key_for_redirect_uri(params[:state])
session[key] if key
end end
end end
end end
---
title: Validate session key when authorizing with GCP to create a cluster
merge_request:
author:
type: security
...@@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do ...@@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do
let(:token) { 'token' } let(:token) { 'token' }
let(:expires_at) { 1.hour.since.strftime('%s') } let(:expires_at) { 1.hour.since.strftime('%s') }
subject { get :callback, params: { code: 'xxx', state: @state } } subject { get :callback, params: { code: 'xxx', state: state } }
before do before do
sign_in(user) sign_in(user)
...@@ -15,24 +15,33 @@ describe GoogleApi::AuthorizationsController do ...@@ -15,24 +15,33 @@ describe GoogleApi::AuthorizationsController do
.to receive(:get_token).and_return([token, expires_at]) .to receive(:get_token).and_return([token, expires_at])
end end
it 'sets token and expires_at in session' do shared_examples_for 'access denied' do
it 'returns a 404' do
subject subject
expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]) expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil
.to eq(token) expect(response).to have_http_status(:not_found)
expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]) end
.to eq(expires_at)
end end
context 'when redirect uri key is stored in state' do context 'session key is present' do
set(:project) { create(:project) } let(:session_key) { 'session-key' }
let(:redirect_uri) { project_clusters_url(project).to_s } let(:redirect_uri) { 'example.com' }
before do before do
@state = GoogleApi::CloudPlatform::Client session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri
.new_session_key_for_redirect_uri do |key|
session[key] = redirect_uri
end end
context 'session key matches state param' do
let(:state) { session_key }
it 'sets token and expires_at in session' do
subject
expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token])
.to eq(token)
expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at])
.to eq(expires_at)
end end
it 'redirects to the URL stored in state param' do it 'redirects to the URL stored in state param' do
...@@ -40,10 +49,23 @@ describe GoogleApi::AuthorizationsController do ...@@ -40,10 +49,23 @@ describe GoogleApi::AuthorizationsController do
end end
end end
context 'when redirection url is not stored in state' do context 'session key does not match state param' do
it 'redirects to root_path' do let(:state) { 'bad-key' }
expect(subject).to redirect_to(root_path)
it_behaves_like 'access denied'
end end
context 'state param is blank' do
let(:state) { '' }
it_behaves_like 'access denied'
end
end
context 'state param is present, but session key is blank' do
let(:state) { 'session-key' }
it_behaves_like 'access denied'
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment