diff options
Diffstat (limited to 'spec/requests/api/internal/kubernetes_spec.rb')
-rw-r--r-- | spec/requests/api/internal/kubernetes_spec.rb | 177 |
1 files changed, 174 insertions, 3 deletions
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index be76e55269a..c07382a6e04 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_management do +RSpec.describe API::Internal::Kubernetes, feature_category: :deployment_management do let(:jwt_auth_headers) do jwt_token = JWT.encode({ 'iss' => Gitlab::Kas::JWT_ISSUER }, Gitlab::Kas.secret, 'HS256') @@ -59,12 +59,29 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme end end + shared_examples 'error handling' do + let!(:agent_token) { create(:cluster_agent_token) } + + # this test verifies fix for an issue where AgentToken passed in Authorization + # header broke error handling in the api_helpers.rb. It can be removed after + # https://gitlab.com/gitlab-org/gitlab/-/issues/406582 is done + it 'returns correct error for the endpoint' do + allow(Gitlab::Kas).to receive(:verify_api_request).and_raise(StandardError.new('Unexpected Error')) + + send_request(headers: { 'Authorization' => "Bearer #{agent_token.token}" }) + + expect(response).to have_gitlab_http_status(:internal_server_error) + expect(response.body).to include("Unexpected Error") + end + end + describe 'POST /internal/kubernetes/usage_metrics', :clean_gitlab_redis_shared_state do def send_request(headers: {}, params: {}) post api('/internal/kubernetes/usage_metrics'), params: params, headers: headers.reverse_merge(jwt_auth_headers) end include_examples 'authorization' + include_examples 'error handling' context 'is authenticated for an agent' do let!(:agent_token) { create(:cluster_agent_token) } @@ -147,19 +164,30 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme projects: [ { id: project.full_path, default_namespace: 'staging' } ] + }, + user_access: { + groups: [ + { id: group.full_path } + ], + projects: [ + { id: project.full_path } + ] } } end include_examples 'authorization' + include_examples 'error handling' context 'agent exists' do it 'configures the agent and returns a 204' do send_request(params: { agent_id: agent.id, agent_config: config }) expect(response).to have_gitlab_http_status(:no_content) - expect(agent.authorized_groups).to contain_exactly(group) - expect(agent.authorized_projects).to contain_exactly(project) + expect(agent.ci_access_authorized_groups).to contain_exactly(group) + expect(agent.ci_access_authorized_projects).to contain_exactly(project) + expect(agent.user_access_authorized_groups).to contain_exactly(group) + expect(agent.user_access_authorized_projects).to contain_exactly(project) end end @@ -179,6 +207,7 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme include_examples 'authorization' include_examples 'agent authentication' + include_examples 'error handling' context 'an agent is found' do let!(:agent_token) { create(:cluster_agent_token) } @@ -223,6 +252,7 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme include_examples 'authorization' include_examples 'agent authentication' + include_examples 'error handling' context 'an agent is found' do let_it_be(:agent_token) { create(:cluster_agent_token) } @@ -306,4 +336,145 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_manageme end end end + + describe 'POST /internal/kubernetes/authorize_proxy_user', :clean_gitlab_redis_sessions do + include SessionHelpers + + def send_request(headers: {}, params: {}) + post api('/internal/kubernetes/authorize_proxy_user'), params: params, headers: headers.reverse_merge(jwt_auth_headers) + end + + def stub_user_session(user, csrf_token) + stub_session( + { + 'warden.user.user.key' => [[user.id], user.authenticatable_salt], + '_csrf_token' => csrf_token + } + ) + end + + def stub_user_session_with_no_user_id(user, csrf_token) + stub_session( + { + 'warden.user.user.key' => [[nil], user.authenticatable_salt], + '_csrf_token' => csrf_token + } + ) + end + + def mask_token(encoded_token) + controller = ActionController::Base.new + raw_token = controller.send(:decode_csrf_token, encoded_token) + controller.send(:mask_token, raw_token) + end + + def new_token + ActionController::Base.new.send(:generate_csrf_token) + end + + let_it_be(:organization) { create(:group) } + let_it_be(:configuration_project) { create(:project, group: organization) } + let_it_be(:agent) { create(:cluster_agent, name: 'the-agent', project: configuration_project) } + let_it_be(:another_agent) { create(:cluster_agent) } + let_it_be(:deployment_project) { create(:project, group: organization) } + let_it_be(:deployment_group) { create(:group, parent: organization) } + + let(:user_access_config) do + { + 'user_access' => { + 'access_as' => { 'agent' => {} }, + 'projects' => [{ 'id' => deployment_project.full_path }], + 'groups' => [{ 'id' => deployment_group.full_path }] + } + } + end + + let(:user) { create(:user) } + + before do + allow(::Gitlab::Kas).to receive(:enabled?).and_return true + Clusters::Agents::Authorizations::UserAccess::RefreshService.new(agent, config: user_access_config).execute + end + + it 'returns 400 when cookie is invalid' do + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: '123', csrf_token: mask_token(new_token) }) + + expect(response).to have_gitlab_http_status(:bad_request) + end + + it 'returns 401 when session is not found' do + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id('abc') + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(new_token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 401 when CSRF token does not match' do + public_id = stub_user_session(user, new_token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(new_token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 404 for non-existent agent' do + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: non_existing_record_id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'returns 403 when user has no access' do + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'returns 200 when user has access' do + deployment_project.add_member(user, :developer) + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:success) + end + + it 'returns 401 when user has valid KAS cookie and CSRF token but has no access to requested agent' do + deployment_project.add_member(user, :developer) + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: another_agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'returns 401 when global flag is disabled' do + stub_feature_flags(kas_user_access: false) + + deployment_project.add_member(user, :developer) + token = new_token + public_id = stub_user_session(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 401 when user id is not found in session' do + deployment_project.add_member(user, :developer) + token = new_token + public_id = stub_user_session_with_no_user_id(user, token) + access_key = Gitlab::Kas::UserAccess.encrypt_public_session_id(public_id) + send_request(params: { agent_id: agent.id, access_type: 'session_cookie', access_key: access_key, csrf_token: mask_token(token) }) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end end |