Skip to content

Enable security policy bot for group policies

What does this MR do and why?

Until now, the security policy bots only get created when a project is directly linked to a security policy project.

With this MR, the policy bots will be created for each project in a group when it gets linked to a security policy project. Due to security considerations, there's a separate bot user for each project to avoid unauthorized cross-project access within a group.

How to set up and validate locally

  1. In rails console enable the experiment fully
    Feature.enable(:scan_execution_group_bot_users)
  2. Create a group
  3. Under this group, create two projects
  4. Go to the group, Policies and create a new scan execution policy. Example: Run secret detection with a schedule on main branch.
  5. Click configure with a merge request
  6. There should be bot users created in each project
  7. Create another project under the same group
  8. The project should have a new bot user with guest access created automatically
  9. Go to Policies and unlink the security policy project
  10. The bot users should get deleted from the projects

Database query

Scope with_security_policy_bots

Used to prevent N+1 queries here: !125411 (diffs)

Plan

Query:

SELECT "members"."id" AS t0_r0, "members"."access_level" AS t0_r1, "members"."source_id" AS t0_r2, "members"."source_type" AS t0_r3, "members"."user_id" AS t0_r4, "members"."notification_level" AS t0_r5, "members"."type" AS t0_r6, "members"."created_at" AS t0_r7, "members"."updated_at" AS t0_r8, "members"."created_by_id" AS t0_r9, "members"."invite_email" AS t0_r10, "members"."invite_token" AS t0_r11, "members"."invite_accepted_at" AS t0_r12, "members"."requested_at" AS t0_r13, "members"."expires_at" AS t0_r14, "members"."ldap" AS t0_r15, "members"."override" AS t0_r16, "members"."state" AS t0_r17, "members"."invite_email_success" AS t0_r18, "members"."member_namespace_id" AS t0_r19, "members"."member_role_id" AS t0_r20, "users"."id" AS t1_r0, "users"."email" AS t1_r1, "users"."encrypted_password" AS t1_r2, "users"."reset_password_token" AS t1_r3, "users"."reset_password_sent_at" AS t1_r4, "users"."remember_created_at" AS t1_r5, "users"."sign_in_count" AS t1_r6, "users"."current_sign_in_at" AS t1_r7, "users"."last_sign_in_at" AS t1_r8, "users"."current_sign_in_ip" AS t1_r9, "users"."last_sign_in_ip" AS t1_r10, "users"."created_at" AS t1_r11, "users"."updated_at" AS t1_r12, "users"."name" AS t1_r13, "users"."admin" AS t1_r14, "users"."projects_limit" AS t1_r15, "users"."failed_attempts" AS t1_r16, "users"."locked_at" AS t1_r17, "users"."username" AS t1_r18, "users"."can_create_group" AS t1_r19, "users"."can_create_team" AS t1_r20, "users"."state" AS t1_r21, "users"."color_scheme_id" AS t1_r22, "users"."password_expires_at" AS t1_r23, "users"."created_by_id" AS t1_r24, "users"."last_credential_check_at" AS t1_r25, "users"."avatar" AS t1_r26, "users"."confirmation_token" AS t1_r27, "users"."confirmed_at" AS t1_r28, "users"."confirmation_sent_at" AS t1_r29, "users"."unconfirmed_email" AS t1_r30, "users"."hide_no_ssh_key" AS t1_r31, "users"."admin_email_unsubscribed_at" AS t1_r32, "users"."notification_email" AS t1_r33, "users"."hide_no_password" AS t1_r34, "users"."password_automatically_set" AS t1_r35, "users"."encrypted_otp_secret" AS t1_r36, "users"."encrypted_otp_secret_iv" AS t1_r37, "users"."encrypted_otp_secret_salt" AS t1_r38, "users"."otp_required_for_login" AS t1_r39, "users"."otp_backup_codes" AS t1_r40, "users"."public_email" AS t1_r41, "users"."dashboard" AS t1_r42, "users"."project_view" AS t1_r43, "users"."consumed_timestep" AS t1_r44, "users"."layout" AS t1_r45, "users"."hide_project_limit" AS t1_r46, "users"."note" AS t1_r47, "users"."unlock_token" AS t1_r48, "users"."otp_grace_period_started_at" AS t1_r49, "users"."external" AS t1_r50, "users"."incoming_email_token" AS t1_r51, "users"."auditor" AS t1_r52, "users"."require_two_factor_authentication_from_group" AS t1_r53, "users"."two_factor_grace_period" AS t1_r54, "users"."last_activity_on" AS t1_r55, "users"."notified_of_own_activity" AS t1_r56, "users"."preferred_language" AS t1_r57, "users"."email_opted_in" AS t1_r58, "users"."email_opted_in_ip" AS t1_r59, "users"."email_opted_in_source_id" AS t1_r60, "users"."email_opted_in_at" AS t1_r61, "users"."theme_id" AS t1_r62, "users"."accepted_term_id" AS t1_r63, "users"."feed_token" AS t1_r64, "users"."private_profile" AS t1_r65, "users"."roadmap_layout" AS t1_r66, "users"."include_private_contributions" AS t1_r67, "users"."commit_email" AS t1_r68, "users"."group_view" AS t1_r69, "users"."managing_group_id" AS t1_r70, "users"."first_name" AS t1_r71, "users"."last_name" AS t1_r72, "users"."static_object_token" AS t1_r73, "users"."role" AS t1_r74, "users"."user_type" AS t1_r75, "users"."static_object_token_encrypted" AS t1_r76, "users"."otp_secret_expires_at" AS t1_r77, "users"."onboarding_in_progress" AS t1_r78 FROM "members" LEFT OUTER JOIN "users" ON "users"."id" = "members"."user_id" WHERE "members"."source_type" = 'Project' AND "members"."type" = 'ProjectMember' AND "members"."requested_at" IS NULL AND "users"."user_type" = 10 AND "members"."source_id" IN (47495114)
 Nested Loop  (cost=1.12..7.18 rows=1 width=1655) (actual time=61.026..61.037 rows=1 loops=1)
   Buffers: shared hit=23 read=27 dirtied=2
   I/O Timings: read=59.593 write=0.000
   ->  Index Scan using index_users_on_user_type_and_id on public.users  (cost=0.56..3.58 rows=1 width=1464) (actual time=10.713..34.341 rows=8 loops=1)
         Index Cond: (users.user_type = 10)
         Buffers: shared hit=3 read=12
         I/O Timings: read=34.049 write=0.000
   ->  Index Scan using idx_members_on_user_and_source_and_source_type_and_member_role on public.members  (cost=0.56..3.59 rows=1 width=191) (actual time=3.327..3.328 rows=0 loops=8)
         Index Cond: ((members.user_id = users.id) AND (members.source_id = 47495114) AND ((members.source_type)::text = 'Project'::text))
         Filter: ((members.requested_at IS NULL) AND ((members.type)::text = 'ProjectMember'::text))
         Rows Removed by Filter: 0
         Buffers: shared hit=20 read=15 dirtied=2
         I/O Timings: read=25.544 write=0.000

Time: 73.538 ms
  - planning: 12.319 ms
  - execution: 61.219 ms
    - I/O read: 59.593 ms
    - I/O write: 0.000 ms

Shared buffers:
  - hits: 23 (~184.00 KiB) from the buffer pool
  - reads: 27 (~216.00 KiB) from the OS file cache, including disk I/O
  - dirtied: 2 (~16.00 KiB)
  - writes: 0

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #414328 (closed)

Edited by Martin Čavoj

Merge request reports