From b1c81e29a83b203415d9c415d84e694aceb39437 Mon Sep 17 00:00:00 2001 From: Tiger Date: Thu, 8 May 2025 16:41:59 +1200 Subject: [PATCH] Allow agent CI access sharing across top level groups Previously a project/group that was authorized to use the CI tunnel had to share a root ancestor with the agent configuration project. On GitLab.com this is typically not a problem, as most projects/groups that would be authorized already belong to the same top level group. However, self-managed installations often structure their hierarchies differently with multiple top level groups, which limits the usefulness of the agent. With this change, any project or group on the instance can be specified in the agent configuration file, regardless of root ancestor. This functionality will not be available on GitLab.com, and is controlled by the `organization_cluster_agent_authorization_enabled` application setting. Changelog: added --- .../agents/authorizations/ci_access/finder.rb | 7 ++-- .../ci_access/refresh_service.rb | 16 +++++---- .../application_settings/_cluster_agents.haml | 2 +- doc/user/clusters/agent/ci_cd_workflow.md | 8 +++-- locale/gitlab.pot | 2 +- .../authorizations/ci_access/finder_spec.rb | 30 +++++++++++------ .../ci_access/refresh_service_spec.rb | 33 +++++++++++++++++-- 7 files changed, 74 insertions(+), 24 deletions(-) diff --git a/app/finders/clusters/agents/authorizations/ci_access/finder.rb b/app/finders/clusters/agents/authorizations/ci_access/finder.rb index 0209cb357e5aac..65ae3dc45ea161 100644 --- a/app/finders/clusters/agents/authorizations/ci_access/finder.rb +++ b/app/finders/clusters/agents/authorizations/ci_access/finder.rb @@ -38,9 +38,12 @@ def project_authorizations .where(project_id: project.id) .joins(agent: :project) .preload(agent: :project) - .where(cluster_agents: { projects: { namespace_id: namespace_ids } }) .with_available_ci_access_fields(project) + unless organization_agents_enabled? + query = query.where(cluster_agents: { projects: { namespace_id: namespace_ids } }) + end + query = query.where(agent_id: agent.id) if agent query.to_a end @@ -64,7 +67,6 @@ def group_authorizations .joins(cte_join_sources) .joins(agent: :project) .with_available_ci_access_fields(project) - .where(projects: { namespace_id: all_namespace_ids }) .order( Arel.sql( 'agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], ' \ @@ -74,6 +76,7 @@ def group_authorizations .select('DISTINCT ON (agent_id) agent_group_authorizations.*') .preload(agent: :project) + query = query.where(projects: { namespace_id: all_namespace_ids }) unless organization_agents_enabled? query = query.where(agent_id: agent.id) if agent query.to_a end diff --git a/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb b/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb index 68d603edb64182..c9622b3c042c69 100644 --- a/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb +++ b/app/services/clusters/agents/authorizations/ci_access/refresh_service.rb @@ -10,7 +10,7 @@ class RefreshService AUTHORIZED_ENTITY_LIMIT = 500 delegate :project, to: :agent, private: true - delegate :root_ancestor, to: :project, private: true + delegate :root_ancestor, :organization, to: :project, private: true def initialize(agent, config:) @agent = agent @@ -59,10 +59,8 @@ def refresh_organization! return unless organization_agents_enabled? if organization_configuration - organization_id = agent.project.organization_id - agent.ci_access_organization_authorizations.upsert_all( - [{ agent_id: agent.id, organization_id: organization_id, config: organization_configuration }], + [{ agent_id: agent.id, organization_id: organization.id, config: organization_configuration }], unique_by: [:agent_id] ) else @@ -107,11 +105,17 @@ def extract_config_entries(entity:) end def allowed_projects - root_ancestor.all_projects + if organization_agents_enabled? + organization.projects + else + root_ancestor.all_projects + end end def allowed_groups - if group_root_ancestor? + if organization_agents_enabled? + organization.groups + elsif group_root_ancestor? root_ancestor.self_and_descendants else ::Group.none diff --git a/app/views/admin/application_settings/_cluster_agents.haml b/app/views/admin/application_settings/_cluster_agents.haml index f639b954715d8a..5990aaf813c61a 100644 --- a/app/views/admin/application_settings/_cluster_agents.haml +++ b/app/views/admin/application_settings/_cluster_agents.haml @@ -11,7 +11,7 @@ = render_if_exists 'admin/application_settings/cluster_agents_receptive_enabled', form: f .form-group - - help_text = s_('ClusterAgents|Allow configuring agents to be authorized for the entire instance.') + - help_text = s_('ClusterAgents|Allow configuring agents to be authorized for the entire instance and across top level groups.') - help_link = link_to _('Learn more.'), help_page_path('user/clusters/agent/ci_cd_workflow.md', anchor: 'authorize-all-projects-in-your-gitlab-instance-to-access-the-agent'), target: '_blank', rel: 'noopener noreferrer' = f.gitlab_ui_checkbox_component :organization_cluster_agent_authorization_enabled, s_('ClusterAgents|Enable instance level authorization'), help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link } diff --git a/doc/user/clusters/agent/ci_cd_workflow.md b/doc/user/clusters/agent/ci_cd_workflow.md index 5753445c8ed377..a319f3813fdef8 100644 --- a/doc/user/clusters/agent/ci_cd_workflow.md +++ b/doc/user/clusters/agent/ci_cd_workflow.md @@ -70,6 +70,7 @@ Authorization configuration can take one or two minutes to propagate. - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/346566) to remove hierarchy restrictions in GitLab 15.6. - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/356831) to allow authorizing projects in a user namespace in GitLab 15.7. +- [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/377932) to allow the authorization of groups that belong to different top-level groups in GitLab 18.1. {{< /history >}} @@ -85,7 +86,8 @@ To authorize the GitLab project where you keep Kubernetes manifests to access th - id: path/to/project ``` - - Authorized projects must have the same top-level group or user namespace as the agent's configuration project. + - Authorized projects must have the same top-level group or user namespace as the agent's configuration project, unless the + [instance level authorization](#authorize-all-projects-in-your-gitlab-instance-to-access-the-agent) application setting is enabled. - You can install additional agents into the same cluster to accommodate additional hierarchies. - You can authorize up to 500 projects. @@ -100,6 +102,7 @@ After making these changes: {{< history >}} - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/346566) to remove hierarchy restrictions in GitLab 15.6. +- [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/377932) to allow the authorization of groups that belong to different top-level groups in GitLab 18.1. {{< /history >}} @@ -115,7 +118,8 @@ To authorize all of the GitLab projects in a group or subgroup to access the age - id: path/to/group/subgroup ``` - - Authorized groups must have the same top-level group as the agent's configuration project. + - Authorized groups must have the same top-level group as the agent's configuration project, unless the + [instance level authorization](#authorize-all-projects-in-your-gitlab-instance-to-access-the-agent) application setting is enabled. - You can install additional agents into the same cluster to accommodate additional hierarchies. - All of the subgroups of an authorized group also have access to the same agent (without being specified individually). - You can authorize up to 500 groups. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7b8e026b0f1ab0..03de239adaee7d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13684,7 +13684,7 @@ msgstr "" msgid "ClusterAgents|Allow configuring agents in receptive mode." msgstr "" -msgid "ClusterAgents|Allow configuring agents to be authorized for the entire instance." +msgid "ClusterAgents|Allow configuring agents to be authorized for the entire instance and across top level groups." msgstr "" msgid "ClusterAgents|An error occurred while loading your agent" diff --git a/spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb b/spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb index 9166439837d3af..99c48c7b8eaebe 100644 --- a/spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb +++ b/spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb @@ -54,13 +54,18 @@ describe 'project authorizations' do context 'when initialized without an agent' do context 'agent configuration project does not share a root namespace with the given project' do - let(:unrelated_agent) { create(:cluster_agent) } - - before do - create(:agent_ci_access_project_authorization, agent: unrelated_agent, project: requesting_project) - end + let_it_be(:unrelated_agent) { create(:cluster_agent) } + let_it_be(:project_authorization) { create(:agent_ci_access_project_authorization, agent: unrelated_agent, project: requesting_project) } it { is_expected.to be_empty } + + context 'when the organization authorization application setting is enabled' do + before do + stub_application_setting(organization_cluster_agent_authorization_enabled: true) + end + + it { is_expected.to match_array([project_authorization]) } + end end context 'agent configuration project shares a root namespace, but does not belong to an ancestor of the given project' do @@ -135,13 +140,18 @@ describe 'authorized groups' do context 'when initialized without an agent' do context 'agent configuration project is outside the requesting project hierarchy' do - let(:unrelated_agent) { create(:cluster_agent) } - - before do - create(:agent_ci_access_group_authorization, agent: unrelated_agent, group: top_level_group) - end + let_it_be(:unrelated_agent) { create(:cluster_agent) } + let_it_be(:project_authorization) { create(:agent_ci_access_group_authorization, agent: unrelated_agent, group: top_level_group) } it { is_expected.to be_empty } + + context 'when the organization authorization application setting is enabled' do + before do + stub_application_setting(organization_cluster_agent_authorization_enabled: true) + end + + it { is_expected.to match_array([project_authorization]) } + end end context 'multiple agents are authorized for the same group' do diff --git a/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb b/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb index 9fcb13fcf3d2e9..0e0fb2b6b4237c 100644 --- a/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb +++ b/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb @@ -14,6 +14,9 @@ let_it_be(:modified_project) { create(:project, namespace: root_ancestor) } let_it_be(:added_project) { create(:project, path: 'project-path-with-UPPERCASE', namespace: root_ancestor) } + let_it_be(:user_project_outside_of_hierarchy) { create(:project) } + let_it_be(:group_project_outside_of_hierarchy) { create(:project, :in_group) } + let(:project) { create(:project, namespace: root_ancestor) } let(:agent) { create(:cluster_agent, project: project) } @@ -61,7 +64,7 @@ end context 'config contains groups outside of the configuration project hierarchy' do - let(:project) { create(:project, namespace: create(:group)) } + let(:project) { group_project_outside_of_hierarchy } it 'removes all authorizations' do expect(subject).to be_truthy @@ -70,7 +73,7 @@ end context 'configuration project does not belong to a group' do - let(:project) { create(:project) } + let(:project) { user_project_outside_of_hierarchy } it 'removes all authorizations' do expect(subject).to be_truthy @@ -92,6 +95,19 @@ 'protected_branches_only' => 'true' }) end + context 'when the organization authorization application setting is enabled' do + let(:project) { group_project_outside_of_hierarchy } + + before do + stub_application_setting(organization_cluster_agent_authorization_enabled: true) + end + + it 'allows authorizing groups outside of the configuration project hierarchy' do + expect(subject).to be_truthy + expect(agent.ci_access_authorized_groups).to contain_exactly(added_group, modified_group) + end + end + context 'config contains too many groups' do before do stub_const("#{described_class}::AUTHORIZED_ENTITY_LIMIT", 1) @@ -131,6 +147,19 @@ end end + context 'when the organization authorization application setting is enabled' do + let(:project) { group_project_outside_of_hierarchy } + + before do + stub_application_setting(organization_cluster_agent_authorization_enabled: true) + end + + it 'allows authorizing groups outside of the configuration project hierarchy' do + expect(subject).to be_truthy + expect(agent.ci_access_authorized_groups).to contain_exactly(added_group, modified_group) + end + end + context 'project does not belong to a group, and is authorizing itself' do let(:root_ancestor) { create(:namespace) } let(:added_project) { project } -- GitLab