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
if state_redirect_uri
redirect_to state_redirect_uri
else
redirect_to root_path
end
end end
private private
def redirect_uri_from_session_key(state) def validate_session_key!
key = GoogleApi::CloudPlatform::Client access_denied! unless redirect_uri_from_session.present?
.session_key_for_redirect_uri(params[:state]) end
session[key] if key
def redirect_uri_from_session
strong_memoize(:redirect_uri_from_session) do
if params[:state].present?
session[session_key_for_redirect_uri(params[:state])]
else
nil
end
end
end
def session_key_for_redirect_uri(state)
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state)
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,35 +15,57 @@ describe GoogleApi::AuthorizationsController do ...@@ -15,35 +15,57 @@ 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
subject it 'returns a 404' do
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| end
session[key] = redirect_uri
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
it 'redirects to the URL stored in state param' do
expect(subject).to redirect_to(redirect_uri)
end end
end end
it 'redirects to the URL stored in state param' do context 'session key does not match state param' do
expect(subject).to redirect_to(redirect_uri) let(:state) { 'bad-key' }
it_behaves_like 'access denied'
end end
end
context 'when redirection url is not stored in state' do context 'state param is blank' do
it 'redirects to root_path' do let(:state) { '' }
expect(subject).to redirect_to(root_path)
it_behaves_like 'access denied'
end end
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
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