Commit 89dc20ad authored by Hordur Freyr Yngvason's avatar Hordur Freyr Yngvason Committed by Steve Abrams

Add License checks to AgentAuthorizationsFinder

The CI/CD tunnel's ci_user and ci_job impersonation features are
EE-only, so these should only be returned when the license is available.
We check the license on the CI job's project under the assumption that
this project and the agent fall under the same license.

Part of https://gitlab.com/gitlab-org/gitlab/-/issues/327848
parent a2091532
...@@ -31,6 +31,7 @@ module Clusters ...@@ -31,6 +31,7 @@ module Clusters
.joins(agent: :project) .joins(agent: :project)
.preload(agent: :project) .preload(agent: :project)
.where(cluster_agents: { projects: { namespace_id: ancestor_ids } }) .where(cluster_agents: { projects: { namespace_id: ancestor_ids } })
.with_available_ci_access_fields(project)
.to_a .to_a
end end
...@@ -53,6 +54,7 @@ module Clusters ...@@ -53,6 +54,7 @@ module Clusters
.joins(cte_join_sources) .joins(cte_join_sources)
.joins(agent: :project) .joins(agent: :project)
.where('projects.namespace_id IN (SELECT id FROM ordered_ancestors)') .where('projects.namespace_id IN (SELECT id FROM ordered_ancestors)')
.with_available_ci_access_fields(project)
.order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)')) .order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)'))
.select('DISTINCT ON (agent_id) agent_group_authorizations.*') .select('DISTINCT ON (agent_id) agent_group_authorizations.*')
.preload(agent: :project) .preload(agent: :project)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Clusters module Clusters
module Agents module Agents
class GroupAuthorization < ApplicationRecord class GroupAuthorization < ApplicationRecord
include ::Clusters::Agents::AuthorizationConfigScopes
self.table_name = 'agent_group_authorizations' self.table_name = 'agent_group_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false belongs_to :agent, class_name: 'Clusters::Agent', optional: false
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Clusters module Clusters
module Agents module Agents
class ProjectAuthorization < ApplicationRecord class ProjectAuthorization < ApplicationRecord
include ::Clusters::Agents::AuthorizationConfigScopes
self.table_name = 'agent_project_authorizations' self.table_name = 'agent_project_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false belongs_to :agent, class_name: 'Clusters::Agent', optional: false
......
# frozen_string_literal: true
module Clusters
module Agents
module AuthorizationConfigScopes
extend ActiveSupport::Concern
included do
scope :with_available_ci_access_fields, ->(project) {
where("config->'access_as' IS NULL")
.or(where("config->'access_as' = '{}'"))
.or(where("config->'access_as' ?| array[:fields]", fields: available_ci_access_fields(project)))
}
end
class_methods do
def available_ci_access_fields(_project)
%w(agent)
end
end
end
end
end
Clusters::Agents::AuthorizationConfigScopes.prepend_mod
# frozen_string_literal: true
module EE
module Clusters
module Agents
module AuthorizationConfigScopes
extend ActiveSupport::Concern
prepended do
class_methods do
alias_method :base_available_ci_access_fields, :available_ci_access_fields
def available_ci_access_fields(project)
base_available_ci_access_fields(project).tap do |fields|
if project.licensed_feature_available?(:cluster_agents_ci_impersonation)
fields << "ci_job"
fields << "ci_user"
fields << "impersonate"
end
end
end
end
end
end
end
end
end
...@@ -68,6 +68,7 @@ class License < ApplicationRecord ...@@ -68,6 +68,7 @@ class License < ApplicationRecord
ci_cd_projects ci_cd_projects
ci_secrets_management ci_secrets_management
cluster_agents_gitops cluster_agents_gitops
cluster_agents_ci_impersonation
cluster_deployments cluster_deployments
code_owner_approval_required code_owner_approval_required
commit_committer_check commit_committer_check
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::AgentAuthorizationsFinder do
describe '#execute' do
let_it_be(:top_level_group) { create(:group) }
let_it_be(:agent_configuration_project) { create(:project, namespace: top_level_group) }
let_it_be(:bottom_level_group) { create(:group, parent: top_level_group) }
let_it_be(:requesting_project, reload: true) { create(:project, namespace: bottom_level_group) }
let_it_be(:production_agent) { create(:cluster_agent, project: agent_configuration_project) }
subject { described_class.new(requesting_project).execute }
shared_examples_for 'licensed access_as' do
context 'impersonate' do
let(:config) { { access_as: { impersonate: {} } } }
it { is_expected.to be_empty }
context 'when available' do
before do
stub_licensed_features(cluster_agents_ci_impersonation: true)
end
it { is_expected.to match_array [authorization] }
end
end
context 'ci_user' do
let(:config) { { access_as: { ci_user: {} } } }
it { is_expected.to be_empty }
context 'when available' do
before do
stub_licensed_features(cluster_agents_ci_impersonation: true)
end
it { is_expected.to match_array [authorization] }
end
end
context 'ci_job' do
let(:config) { { access_as: { ci_job: {} } } }
it { is_expected.to be_empty }
context 'when available' do
before do
stub_licensed_features(cluster_agents_ci_impersonation: true)
end
it { is_expected.to match_array [authorization] }
end
end
end
describe 'project authorizations' do
it_behaves_like 'licensed access_as' do
let!(:authorization) do
create(
:agent_project_authorization,
agent: production_agent,
project: requesting_project,
config: config
)
end
end
end
describe 'group authorizations' do
it_behaves_like 'licensed access_as' do
let!(:authorization) do
create(
:agent_group_authorization,
agent: production_agent,
group: top_level_group,
config: config
)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::Clusters::Agents::AuthorizationConfigScopes do
describe '.with_available_ci_access_fields' do
let_it_be(:project) { create(:project) }
let_it_be(:agent_authorization_0) { create(:agent_project_authorization, project: project) }
let_it_be(:agent_authorization_1) { create(:agent_project_authorization, project: project, config: { access_as: {} }) }
let_it_be(:agent_authorization_2) { create(:agent_project_authorization, project: project, config: { access_as: { agent: {} } }) }
let_it_be(:impersonate_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { impersonate: {} } }) }
let_it_be(:ci_user_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { ci_user: {} } }) }
let_it_be(:ci_job_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { ci_job: {} } }) }
subject { Clusters::Agents::ProjectAuthorization.with_available_ci_access_fields(project) }
it { is_expected.not_to include(ci_job_authorization)}
it { is_expected.not_to include(ci_user_authorization)}
it { is_expected.not_to include(impersonate_authorization)}
context 'with :cluster_agents_ci_impersonation' do
before do
stub_licensed_features(cluster_agents_ci_impersonation: true)
end
it { is_expected.to include(ci_job_authorization, ci_user_authorization, impersonate_authorization) }
end
end
end
...@@ -17,6 +17,34 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do ...@@ -17,6 +17,34 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
subject { described_class.new(requesting_project).execute } subject { described_class.new(requesting_project).execute }
shared_examples_for 'access_as' do
let(:config) { { access_as: { access_as => {} } } }
context 'agent' do
let(:access_as) { :agent }
it { is_expected.to match_array [authorization] }
end
context 'impersonate' do
let(:access_as) { :impersonate }
it { is_expected.to be_empty }
end
context 'ci_user' do
let(:access_as) { :ci_user }
it { is_expected.to be_empty }
end
context 'ci_job' do
let(:access_as) { :ci_job }
it { is_expected.to be_empty }
end
end
describe 'project authorizations' do describe 'project authorizations' do
context 'agent configuration project does not share a root namespace with the given project' do context 'agent configuration project does not share a root namespace with the given project' do
let(:unrelated_agent) { create(:cluster_agent) } let(:unrelated_agent) { create(:cluster_agent) }
...@@ -29,7 +57,7 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do ...@@ -29,7 +57,7 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
end end
context 'with project authorizations present' do context 'with project authorizations present' do
let!(:authorization) {create(:agent_project_authorization, agent: production_agent, project: requesting_project) } let!(:authorization) { create(:agent_project_authorization, agent: production_agent, project: requesting_project) }
it { is_expected.to match_array [authorization] } it { is_expected.to match_array [authorization] }
end end
...@@ -41,6 +69,10 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do ...@@ -41,6 +69,10 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
it { is_expected.to match_array [project_authorization] } it { is_expected.to match_array [project_authorization] }
end end
it_behaves_like 'access_as' do
let!(:authorization) { create(:agent_project_authorization, agent: production_agent, project: requesting_project, config: config) }
end
end end
describe 'implicit authorizations' do describe 'implicit authorizations' do
...@@ -83,6 +115,10 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do ...@@ -83,6 +115,10 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
expect(subject).to contain_exactly(bottom_level_auth) expect(subject).to contain_exactly(bottom_level_auth)
end end
end end
it_behaves_like 'access_as' do
let!(:authorization) { create(:agent_group_authorization, agent: production_agent, group: top_level_group, config: config) }
end
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Agents::AuthorizationConfigScopes do
describe '.with_available_ci_access_fields' do
let(:project) { create(:project) }
let!(:agent_authorization_0) { create(:agent_project_authorization, project: project) }
let!(:agent_authorization_1) { create(:agent_project_authorization, project: project, config: { access_as: {} }) }
let!(:agent_authorization_2) { create(:agent_project_authorization, project: project, config: { access_as: { agent: {} } }) }
let!(:impersonate_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { impersonate: {} } }) }
let!(:ci_user_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { ci_user: {} } }) }
let!(:ci_job_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { ci_job: {} } }) }
let!(:unexpected_authorization) { create(:agent_project_authorization, project: project, config: { access_as: { unexpected: {} } }) }
subject { Clusters::Agents::ProjectAuthorization.with_available_ci_access_fields(project) }
it { is_expected.to contain_exactly(agent_authorization_0, agent_authorization_1, agent_authorization_2) }
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