Merge branch 'git-http-push-check' into 'master'
Stop 'git push' over HTTP early Before this change we always let users push Git data over HTTP before deciding whether to accept to push. This was different from pushing over SSH where we terminate a 'git push' early if we already know the user is not allowed to push. This change let Git over HTTP follow the same behavior as Git over SSH. We also distinguish between HTTP 404 and 403 responses when denying Git requests, depending on whether the user is allowed to know the project exists. See merge request !5639
This commit is contained in:
commit
86c081f71f
4 changed files with 34 additions and 29 deletions
|
@ -20,9 +20,9 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||||
elsif receive_pack? && receive_pack_allowed?
|
elsif receive_pack? && receive_pack_allowed?
|
||||||
render_ok
|
render_ok
|
||||||
elsif http_blocked?
|
elsif http_blocked?
|
||||||
render_not_allowed
|
render_http_not_allowed
|
||||||
else
|
else
|
||||||
render_not_found
|
render_denied
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||||
if upload_pack? && upload_pack_allowed?
|
if upload_pack? && upload_pack_allowed?
|
||||||
render_ok
|
render_ok
|
||||||
else
|
else
|
||||||
render_not_found
|
render_denied
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||||
if receive_pack? && receive_pack_allowed?
|
if receive_pack? && receive_pack_allowed?
|
||||||
render_ok
|
render_ok
|
||||||
else
|
else
|
||||||
render_not_found
|
render_denied
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -156,8 +156,17 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||||
render plain: 'Not Found', status: :not_found
|
render plain: 'Not Found', status: :not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_not_allowed
|
def render_http_not_allowed
|
||||||
render plain: download_access.message, status: :forbidden
|
render plain: access_check.message, status: :forbidden
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_denied
|
||||||
|
if user && user.can?(:read_project, project)
|
||||||
|
render plain: 'Access denied', status: :forbidden
|
||||||
|
else
|
||||||
|
# Do not leak information about project existence
|
||||||
|
render_not_found
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def ci?
|
def ci?
|
||||||
|
@ -168,22 +177,20 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||||
return false unless Gitlab.config.gitlab_shell.upload_pack
|
return false unless Gitlab.config.gitlab_shell.upload_pack
|
||||||
|
|
||||||
if user
|
if user
|
||||||
download_access.allowed?
|
access_check.allowed?
|
||||||
else
|
else
|
||||||
ci? || project.public?
|
ci? || project.public?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def access
|
def access
|
||||||
return @access if defined?(@access)
|
@access ||= Gitlab::GitAccess.new(user, project, 'http')
|
||||||
|
|
||||||
@access = Gitlab::GitAccess.new(user, project, 'http')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def download_access
|
def access_check
|
||||||
return @download_access if defined?(@download_access)
|
# Use the magic string '_any' to indicate we do not know what the
|
||||||
|
# changes are. This is also what gitlab-shell does.
|
||||||
@download_access = access.check('git-upload-pack')
|
@access_check ||= access.check(git_command, '_any')
|
||||||
end
|
end
|
||||||
|
|
||||||
def http_blocked?
|
def http_blocked?
|
||||||
|
@ -193,8 +200,6 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||||
def receive_pack_allowed?
|
def receive_pack_allowed?
|
||||||
return false unless Gitlab.config.gitlab_shell.receive_pack
|
return false unless Gitlab.config.gitlab_shell.receive_pack
|
||||||
|
|
||||||
# Skip user authorization on upload request.
|
access_check.allowed?
|
||||||
# It will be done by the pre-receive hook in the repository.
|
|
||||||
user.present?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ module Gitlab
|
||||||
@user_access = UserAccess.new(user, project: project)
|
@user_access = UserAccess.new(user, project: project)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check(cmd, changes = nil)
|
def check(cmd, changes)
|
||||||
return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
|
return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
|
||||||
|
|
||||||
unless actor
|
unless actor
|
||||||
|
|
|
@ -19,11 +19,11 @@ describe Gitlab::GitAccess, lib: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'blocks ssh git push' do
|
it 'blocks ssh git push' do
|
||||||
expect(@acc.check('git-receive-pack').allowed?).to be_falsey
|
expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'blocks ssh git pull' do
|
it 'blocks ssh git pull' do
|
||||||
expect(@acc.check('git-upload-pack').allowed?).to be_falsey
|
expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,17 +34,17 @@ describe Gitlab::GitAccess, lib: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'blocks http push' do
|
it 'blocks http push' do
|
||||||
expect(@acc.check('git-receive-pack').allowed?).to be_falsey
|
expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'blocks http git pull' do
|
it 'blocks http git pull' do
|
||||||
expect(@acc.check('git-upload-pack').allowed?).to be_falsey
|
expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'download_access_check' do
|
describe 'download_access_check' do
|
||||||
subject { access.check('git-upload-pack') }
|
subject { access.check('git-upload-pack', '_any') }
|
||||||
|
|
||||||
describe 'master permissions' do
|
describe 'master permissions' do
|
||||||
before { project.team << [user, :master] }
|
before { project.team << [user, :master] }
|
||||||
|
@ -288,7 +288,7 @@ describe Gitlab::GitAccess, lib: true do
|
||||||
let(:actor) { key }
|
let(:actor) { key }
|
||||||
|
|
||||||
context 'push code' do
|
context 'push code' do
|
||||||
subject { access.check('git-receive-pack') }
|
subject { access.check('git-receive-pack', '_any') }
|
||||||
|
|
||||||
context 'when project is authorized' do
|
context 'when project is authorized' do
|
||||||
before { key.projects << project }
|
before { key.projects << project }
|
||||||
|
|
|
@ -75,9 +75,9 @@ describe 'Git HTTP requests', lib: true do
|
||||||
context "with correct credentials" do
|
context "with correct credentials" do
|
||||||
let(:env) { { user: user.username, password: user.password } }
|
let(:env) { { user: user.username, password: user.password } }
|
||||||
|
|
||||||
it "uploads get status 200 (because Git hooks do the real check)" do
|
it "uploads get status 403" do
|
||||||
upload(path, env) do |response|
|
upload(path, env) do |response|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(403)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ describe 'Git HTTP requests', lib: true do
|
||||||
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
|
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
|
||||||
|
|
||||||
upload(path, env) do |response|
|
upload(path, env) do |response|
|
||||||
expect(response).to have_http_status(404)
|
expect(response).to have_http_status(403)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -236,9 +236,9 @@ describe 'Git HTTP requests', lib: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "uploads get status 200 (because Git hooks do the real check)" do
|
it "uploads get status 404" do
|
||||||
upload(path, user: user.username, password: user.password) do |response|
|
upload(path, user: user.username, password: user.password) do |response|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(404)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue