Make group name lookup limitable by root container
What does this MR do and why?
Scan result policies allow specifying groups as approvers by specifying their name. Groups are identified by substring matching. Currently, groups are queried by their name globally. This is a problem:
Groups named
compliance
are matched but from a lot of different namespaces:
anthonybaer/gitlab-evaluation/compliance
gaia-x/lab/compliance
gitlab-org/govern/compliance
pensando/tbd/compliance
gitlab-gold/briecarranza/tickets/compliance
blee-group/demos/security/compliance
This MR:
- introduces a new application setting
security_policy_global_group_approvers_enabled
which is enabled by default.- when enabled, groups are queried globally
- when disabled, groups are queried within a container's namespace hierarchy
- it is enabled by default so that the current behaviour is preserved. We'll disable the flag for SaaS.
- pushes the application setting and the root namespace path to the frontend, which will issue different GraphQL queries depending on the setting in the future.
How to set up and validate locally
- Disable the application setting, which is enabled by default:
Gitlab::CurrentSettings.update_attribute(:security_policy_global_group_approvers_enabled, false)
- Create a new group
nameless-bird
- Add any member to the group and assign the "Developer" role
- Create another top-level group
purple-frog
- Create a new subgroup
purple-frog/nameless-bird
- Add any other member to the subgroup and assign the "Developer" role
- Create a new project
purple-frog/nameless-bird/example
- Within the project, navigate to
Security & Compliance > Policies
, click "New policy" and create a new Scan Result Policy:
type: scan_result_policy
name: Dependency Scanning
description: ''
enabled: true
rules:
- type: scan_finding
branches: []
scanners:
- dependency_scanning
vulnerabilities_allowed: 0
severity_levels:
- critical
- high
- medium
- low
- unknown
- info
vulnerability_states:
- newly_detected
- detected
- confirmed
- dismissed
- resolved
actions:
- type: require_approval
approvals_required: 1
group_approvers: ["nameless-bird"]
- Within the project, navigate to
Security & Compliance > Policies
, click on the "Dependency Scanning" policy and verify that the sidebar lists onlypurple-frog/nameless-bird
as approvers - Create a new MR that adds
Gemfile.lock
with the following contents:
GEM
remote: https://rubygems.org/
specs:
rack (0.1.0)
PLATFORMS
x86_64-darwin-20
DEPENDENCIES
rack (= 0.1.0)
BUNDLED WITH
2.3.22
- Verify that the merge request approval rules lists members from
purple-frog/nameless-bird
, not the top-levelnameless-bird
Database queries
Toggle SQL query
SELECT
"namespaces".*
FROM
(
(
SELECT
"namespaces".*
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND (
traversal_ids @> ('{9878214}')
)
AND 1 = 0
)
UNION
(
SELECT
"namespaces".*
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND (
traversal_ids @> ('{9878214}')
)
AND "namespaces"."id" IN (
SELECT
"routes"."namespace_id"
FROM
"routes"
WHERE
"routes"."source_type" = 'Namespace'
AND (
LOWER(routes.path) IN ('dependencies', 'analyzers')
)
)
)
UNION
(
SELECT
"namespaces".*
FROM
"namespaces"
WHERE
"namespaces"."type" = 'Group'
AND (
traversal_ids @> ('{9878214}')
)
AND (
LOWER(path) IN ('dependencies', 'analyzers')
)
)
) namespaces
WHERE
"namespaces"."type" = 'Group'
AND (
"namespaces"."visibility_level" IN (10, 20)
OR EXISTS (
SELECT
1
FROM
(
SELECT
"namespaces".*
FROM
(
(
WITH "direct_groups" AS MATERIALIZED (
SELECT
"namespaces".*
FROM
(
(
SELECT
"namespaces"."id",
"namespaces"."name",
"namespaces"."path",
"namespaces"."owner_id",
"namespaces"."created_at",
"namespaces"."updated_at",
"namespaces"."type",
"namespaces"."description",
"namespaces"."avatar",
"namespaces"."membership_lock",
"namespaces"."share_with_group_lock",
"namespaces"."visibility_level",
"namespaces"."request_access_enabled",
"namespaces"."ldap_sync_status",
"namespaces"."ldap_sync_error",
"namespaces"."ldap_sync_last_update_at",
"namespaces"."ldap_sync_last_successful_update_at",
"namespaces"."ldap_sync_last_sync_at",
"namespaces"."lfs_enabled",
"namespaces"."description_html",
"namespaces"."parent_id",
"namespaces"."shared_runners_minutes_limit",
"namespaces"."repository_size_limit",
"namespaces"."require_two_factor_authentication",
"namespaces"."two_factor_grace_period",
"namespaces"."cached_markdown_version",
"namespaces"."project_creation_level",
"namespaces"."runners_token",
"namespaces"."file_template_project_id",
"namespaces"."saml_discovery_token",
"namespaces"."runners_token_encrypted",
"namespaces"."custom_project_templates_group_id",
"namespaces"."auto_devops_enabled",
"namespaces"."extra_shared_runners_minutes_limit",
"namespaces"."last_ci_minutes_notification_at",
"namespaces"."last_ci_minutes_usage_notification_level",
"namespaces"."subgroup_creation_level",
"namespaces"."emails_disabled",
"namespaces"."max_pages_size",
"namespaces"."max_artifacts_size",
"namespaces"."mentions_disabled",
"namespaces"."default_branch_protection",
"namespaces"."unlock_membership_to_ldap",
"namespaces"."max_personal_access_token_lifetime",
"namespaces"."push_rule_id",
"namespaces"."shared_runners_enabled",
"namespaces"."allow_descendants_override_disabled_shared_runners",
"namespaces"."traversal_ids"
FROM
"namespaces"
INNER JOIN "members" ON "namespaces"."id" = "members"."source_id"
WHERE
"members"."type" = 'GroupMember'
AND "members"."source_type" = 'Namespace'
AND "namespaces"."type" = 'Group'
AND "members"."user_id" = 9878214
AND "members"."requested_at" IS NULL
AND (access_level >= 10)
)
UNION
(
SELECT
"namespaces"."id",
"namespaces"."name",
"namespaces"."path",
"namespaces"."owner_id",
"namespaces"."created_at",
"namespaces"."updated_at",
"namespaces"."type",
"namespaces"."description",
"namespaces"."avatar",
"namespaces"."membership_lock",
"namespaces"."share_with_group_lock",
"namespaces"."visibility_level",
"namespaces"."request_access_enabled",
"namespaces"."ldap_sync_status",
"namespaces"."ldap_sync_error",
"namespaces"."ldap_sync_last_update_at",
"namespaces"."ldap_sync_last_successful_update_at",
"namespaces"."ldap_sync_last_sync_at",
"namespaces"."lfs_enabled",
"namespaces"."description_html",
"namespaces"."parent_id",
"namespaces"."shared_runners_minutes_limit",
"namespaces"."repository_size_limit",
"namespaces"."require_two_factor_authentication",
"namespaces"."two_factor_grace_period",
"namespaces"."cached_markdown_version",
"namespaces"."project_creation_level",
"namespaces"."runners_token",
"namespaces"."file_template_project_id",
"namespaces"."saml_discovery_token",
"namespaces"."runners_token_encrypted",
"namespaces"."custom_project_templates_group_id",
"namespaces"."auto_devops_enabled",
"namespaces"."extra_shared_runners_minutes_limit",
"namespaces"."last_ci_minutes_notification_at",
"namespaces"."last_ci_minutes_usage_notification_level",
"namespaces"."subgroup_creation_level",
"namespaces"."emails_disabled",
"namespaces"."max_pages_size",
"namespaces"."max_artifacts_size",
"namespaces"."mentions_disabled",
"namespaces"."default_branch_protection",
"namespaces"."unlock_membership_to_ldap",
"namespaces"."max_personal_access_token_lifetime",
"namespaces"."push_rule_id",
"namespaces"."shared_runners_enabled",
"namespaces"."allow_descendants_override_disabled_shared_runners",
"namespaces"."traversal_ids"
FROM
"projects"
INNER JOIN "project_authorizations" ON "projects"."id" = "project_authorizations"."project_id"
INNER JOIN "namespaces" ON "namespaces"."id" = "projects"."namespace_id"
WHERE
"project_authorizations"."user_id" = 9878214
)
) namespaces
WHERE
"namespaces"."type" = 'Group'
)
SELECT
"namespaces".*
FROM
(
(
SELECT
"namespaces".*
FROM
"direct_groups" "namespaces"
WHERE
"namespaces"."type" = 'Group'
)
UNION
(
SELECT
"namespaces".*
FROM
"namespaces"
INNER JOIN "group_group_links" ON "group_group_links"."shared_group_id" = "namespaces"."id"
WHERE
"namespaces"."type" = 'Group'
AND "group_group_links"."shared_with_group_id" IN (
SELECT
"namespaces"."id"
FROM
"direct_groups" "namespaces"
WHERE
"namespaces"."type" = 'Group'
)
)
) namespaces
WHERE
"namespaces"."type" = 'Group'
)
) namespaces
WHERE
"namespaces"."type" = 'Group'
) authorized
WHERE
authorized."id" = "namespaces"."id"
)
);
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Related to #378234 (closed)