diff --git a/app/controllers/user_settings/ssh_keys_controller.rb b/app/controllers/user_settings/ssh_keys_controller.rb index a2dbbc49eefdd2be1119bf2a616097708df32f85..0e236e5c8eb68744511ef0562ce8b59971b74098 100644 --- a/app/controllers/user_settings/ssh_keys_controller.rb +++ b/app/controllers/user_settings/ssh_keys_controller.rb @@ -53,3 +53,5 @@ def key_params end end end + +UserSettings::SshKeysController.prepend_mod diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 9bb048dbec5f739fb41bb08b5f53ad7e7a0a2628..406cada721cd3c7242391069cacde8c6f80eb70a 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -42,6 +42,7 @@ .label-description = s_('GroupSettings|Settings that apply only to enterprise users associated with this group.') = render_if_exists 'groups/settings/enterprise_users_pats', f: f, group: @group + = render_if_exists 'groups/settings/enterprise_users_ssh_keys', f: f, group: @group = render_if_exists 'groups/settings/hide_email_on_profile', f: f, group: @group = render_if_exists 'groups/settings/extensions_marketplace', f: f, group: @group = render_if_exists 'groups/settings/allow_personal_snippets', f: f, group: @group diff --git a/db/migrate/20251017112724_add_disable_ssh_keys_to_namespace_settings.rb b/db/migrate/20251017112724_add_disable_ssh_keys_to_namespace_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..3901f9225191987defb49f468b204a9887d21de2 --- /dev/null +++ b/db/migrate/20251017112724_add_disable_ssh_keys_to_namespace_settings.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddDisableSshKeysToNamespaceSettings < Gitlab::Database::Migration[2.3] + milestone '18.6' + + def change + add_column :namespace_settings, :disable_ssh_keys, :boolean, default: false, null: false + end + + def down + remove_column :namespace_settings, :disable_ssh_keys + end +end diff --git a/db/schema_migrations/20251017112724 b/db/schema_migrations/20251017112724 new file mode 100644 index 0000000000000000000000000000000000000000..c9f33fbff18f8dc128eb389550559d5dad074933 --- /dev/null +++ b/db/schema_migrations/20251017112724 @@ -0,0 +1 @@ +855c1e14448e13ebe7bb955cf6332605a0b12443f342dc7666f6dec0c19863fb \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 546e29010afde50987c44e1204ad1e725b6fdaf2..f556484827e0a4c334c23b87a8030d4d3093b5c7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -20508,6 +20508,7 @@ CREATE TABLE namespace_settings ( duo_remote_flows_enabled boolean, lock_duo_remote_flows_enabled boolean DEFAULT false NOT NULL, duo_agent_platform_request_count integer DEFAULT 0 NOT NULL, + disable_ssh_keys boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)), CONSTRAINT check_d9644d516f CHECK ((char_length(step_up_auth_required_oauth_provider) <= 255)), CONSTRAINT check_namespace_settings_security_policies_is_hash CHECK ((jsonb_typeof(security_policies) = 'object'::text)), diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index f9619c49fa92929aac6b448c79ca479cb2fdde27..cf7042898a34f8e904db21171d98191dc9de9007 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -634,6 +634,7 @@ Audit event types belong to the following product categories. | [`allowed_email_domain_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166105) | Group setting allowed email domain entry is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.5](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | | [`application_setting_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124639) | An application setting is updated | {{< icon name="check-circle" >}} Yes | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/282428) | Instance | | [`disable_personal_access_tokens_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973) | Setting Disable personal access tokens is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | +| [`disable_ssh_keys_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205020) | Setting Disable SSH Keys is updated | {{< icon name="check-circle" >}} Yes | GitLab [18.6](https://gitlab.com/gitlab-org/gitlab/-/issues/30343) | Group | | [`email_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114546) | An email is created | {{< icon name="check-circle" >}} Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User | | [`email_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114546) | An email is destroyed | {{< icon name="check-circle" >}} Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374107) | User | | [`group_access_token_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92225) | A group access token is created | {{< icon name="check-circle" >}} Yes | GitLab [15.2](https://gitlab.com/gitlab-org/gitlab/-/issues/363087) | Group | diff --git a/doc/user/enterprise_user/_index.md b/doc/user/enterprise_user/_index.md index 93aaf71beb9b2a175dc80f37619db5267c973ab2..fabf1c6ea87019729f5b2f38e33292248bb60658 100644 --- a/doc/user/enterprise_user/_index.md +++ b/doc/user/enterprise_user/_index.md @@ -190,6 +190,7 @@ reduce the security footprint of your users. - [Disable password authentication](../group/saml_sso/_index.md#disable-password-authentication-for-enterprise-users). - [Disable personal access tokens](../../user/profile/personal_access_tokens.md#disable-personal-access-tokens-for-enterprise-users). +- [Disable SSH Keys](../../user/ssh.md#disable-ssh-keys-for-enterprise-users). - [Disable two-factor authentication](../../security/two_factor_authentication.md#enterprise-users). ### Restrict group and project creation @@ -253,11 +254,14 @@ the user, you can choose to either: ### Release an enterprise user -You can remove enterprise management features from a user account. Removing an enterprise user might be required if, for -example, a user wants to keep their GitLab account after leaving their company. Releasing a user -does not alter their account roles or permissions, but removes the management options -for the group Owner. If you need to permanently remove the account, you should -[delete the user](#delete-an-enterprise-user) instead. +You can remove enterprise management features from a user account. You might need to +do this if, for example, a user wants to keep their GitLab account after leaving their +company. When you release a user, their account roles and permissions remain the same, +but the group Owner loses management options for that user. For example, the released +user can access authentication methods that the group Owner previously disabled. + +If you need to permanently remove the account, [delete the user](#delete-an-enterprise-user) +instead. To release the user, GitLab support must update the user's primary email address to an email address from a non-verified domain. This action automatically releases the account. diff --git a/doc/user/ssh.md b/doc/user/ssh.md index 9b71ada736b73c2332770e4e911e770d07d6515e..ea8526e5d3d4fe2fa588ed87361fa612400f2b24 100644 --- a/doc/user/ssh.md +++ b/doc/user/ssh.md @@ -547,6 +547,47 @@ Alternative tools include: - [Cygwin](https://www.cygwin.com) - [PuTTYgen](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) 0.81 and later (earlier versions are [vulnerable to disclosure attacks](https://www.openwall.com/lists/oss-security/2024/04/15/6)) +## Disable SSH Keys for enterprise users + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30343) in GitLab 18.6 [with a flag](../administration/feature_flags/_index.md) named `enterprise_disable_ssh_keys`. Disabled by default. + +{{< /history >}} + +{{< alert type="flag" >}} + +The availability of this feature is controlled by a feature flag. +For more information, see the history. + +{{< /alert >}} + +Prerequisites: + +- You must have the Owner role for the group that the enterprise user belongs to. + +Disabling the SSH Keys of a group's [enterprise users](enterprise_user/_index.md): + +- Stops the enterprise users from adding new SSH Keys. This behavior applies + even if an enterprise user is also an administrator of the group. +- Disables the existing SSH Keys of the enterprise users. + +{{< alert type="warning" >}} + +Disabling SSH Keys for enterprise users does not disable deployment keys for [service accounts](profile/service_accounts.md). + +{{< /alert >}} + +To disable the enterprise users' SSH Keys: + +1. On the left sidebar, select **Search or go to** and find your group. +1. Select **Settings > General**. +1. Expand **Permissions and group features**. +1. Under **Enterprise users**, select **Disable SSH Keys**. +1. Select **Save changes**. + +When you delete or block an enterprise user account, their personal SSH Keys are automatically revoked. + ## Overriding SSH settings on the GitLab server GitLab integrates with the system-installed SSH daemon and designates a user diff --git a/ee/app/controllers/concerns/ee/groups/params.rb b/ee/app/controllers/concerns/ee/groups/params.rb index b132caf70cf38f96d925f27b887a695de25a5b1b..0c260c6fabf0b2e96e78efdb469368e79a788f37 100644 --- a/ee/app/controllers/concerns/ee/groups/params.rb +++ b/ee/app/controllers/concerns/ee/groups/params.rb @@ -74,6 +74,7 @@ def group_params_ee end params_ee << :disable_personal_access_tokens if current_group&.disable_personal_access_tokens_available? + params_ee << :disable_ssh_keys if current_group&.disable_ssh_keys_available? if current_group&.can_manage_extensions_marketplace_for_enterprise_users? params_ee << :enterprise_users_extensions_marketplace_enabled diff --git a/ee/app/controllers/ee/user_settings/ssh_keys_controller.rb b/ee/app/controllers/ee/user_settings/ssh_keys_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..68e7bd6943d6aa42bb9f658d97ddc4329375d225 --- /dev/null +++ b/ee/app/controllers/ee/user_settings/ssh_keys_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module EE + module UserSettings + module SshKeysController + extend ActiveSupport::Concern + + prepended do + before_action :check_ssh_keys_enabled, only: [:index, :show, :create, :destroy] + end + + private + + def check_ssh_keys_enabled + return unless current_user.enterprise_user? && current_user.enterprise_group.disable_ssh_keys? + + render_404 + end + end + end +end diff --git a/ee/app/models/ee/group.rb b/ee/app/models/ee/group.rb index ef504b75584334cd8574a1df8e17c0d62925be70..82a9c899c0620ab05ed9817a71a4d26e6b690bd3 100644 --- a/ee/app/models/ee/group.rb +++ b/ee/app/models/ee/group.rb @@ -116,6 +116,7 @@ module Group delegate :user_cap_enabled?, to: :namespace_settings delegate :disable_personal_access_tokens=, to: :namespace_settings + delegate :disable_ssh_keys=, to: :namespace_settings delegate :hide_email_on_profile=, to: :namespace_settings delegate :hide_email_on_profile?, to: :namespace_settings delegate :allow_personal_snippets, to: :namespace_settings @@ -1074,6 +1075,19 @@ def disable_personal_access_tokens? namespace_settings.disable_personal_access_tokens? end + def disable_ssh_keys_available? + root? && + ::Feature.enabled?(:enterprise_disable_ssh_keys, self, type: :gitlab_com_derisk) && + ::Gitlab::Saas.feature_available?(:disable_ssh_keys) && + licensed_feature_available?(:disable_ssh_keys) + end + + # Disable SSH Keys for enterprise users of this group + def disable_ssh_keys? + disable_ssh_keys_available? && + namespace_settings.disable_ssh_keys? + end + def extended_grat_expiry_webhooks_execute? licensed_feature_available?(:group_webhooks) && namespace_settings&.extended_grat_expiry_webhooks_execute? diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb index 95d80f4eb607d2575fdb16b6a992a7069c7c078e..63e82546676694448588a68c3969e5bc4abd2507 100644 --- a/ee/app/models/gitlab_subscriptions/features.rb +++ b/ee/app/models/gitlab_subscriptions/features.rb @@ -120,6 +120,7 @@ class Features disable_extensions_marketplace_for_enterprise_users disable_name_update_for_users disable_personal_access_tokens + disable_ssh_keys domain_verification epic_colors epics diff --git a/ee/app/views/groups/settings/_enterprise_users_ssh_keys.html.haml b/ee/app/views/groups/settings/_enterprise_users_ssh_keys.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..1b0e0309d7939405cf21590595e94ef7e752de32 --- /dev/null +++ b/ee/app/views/groups/settings/_enterprise_users_ssh_keys.html.haml @@ -0,0 +1,8 @@ +- return unless group.disable_ssh_keys_available? + += f.gitlab_ui_checkbox_component :disable_ssh_keys, checkbox_options: { checked: group.disable_ssh_keys? } do |c| + - c.with_label do + = s_('GroupSettings|Disable SSH Keys') + - c.with_help_text do + - learn_more_link = link_to(_('Learn more'), help_page_path('user/ssh.md', anchor: 'disable-ssh-keys-for-enterprise-users')) + = safe_format(s_("GroupSettings|If enabled, enterprise users cannot use SSH Keys. %{learn_more_link}."), learn_more_link: learn_more_link) diff --git a/ee/config/audit_events/types/disable_ssh_keys_updated.yml b/ee/config/audit_events/types/disable_ssh_keys_updated.yml new file mode 100644 index 0000000000000000000000000000000000000000..636ab90e681efb9bfb509b7235daeeee168fcbe8 --- /dev/null +++ b/ee/config/audit_events/types/disable_ssh_keys_updated.yml @@ -0,0 +1,10 @@ +--- +name: disable_ssh_keys_updated +description: Setting Disable SSH Keys is updated +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/30343 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205020 +feature_category: system_access +milestone: '18.6' +saved_to_database: true +streamed: true +scope: [Group] diff --git a/ee/config/feature_flags/gitlab_com_derisk/enterprise_disable_ssh_keys.yml b/ee/config/feature_flags/gitlab_com_derisk/enterprise_disable_ssh_keys.yml new file mode 100644 index 0000000000000000000000000000000000000000..34d21e5bb71ce791ad5a652cf1055dc61e60c855 --- /dev/null +++ b/ee/config/feature_flags/gitlab_com_derisk/enterprise_disable_ssh_keys.yml @@ -0,0 +1,9 @@ +--- +name: enterprise_disable_ssh_keys +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/30343 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205020 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/572256 +milestone: '18.6' +group: group::authentication +type: gitlab_com_derisk +default_enabled: false \ No newline at end of file diff --git a/ee/config/saas_features/disable_ssh_keys.yml b/ee/config/saas_features/disable_ssh_keys.yml new file mode 100644 index 0000000000000000000000000000000000000000..3f63e8f37e03e4ba598d640d007db1459496f7f9 --- /dev/null +++ b/ee/config/saas_features/disable_ssh_keys.yml @@ -0,0 +1,5 @@ +--- +name: disable_ssh_keys +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205020 +milestone: '18.6' +group: group::authentication \ No newline at end of file diff --git a/ee/lib/ee/gitlab/git_access_project.rb b/ee/lib/ee/gitlab/git_access_project.rb index 1089c2496ed38040291cc1bec2ecaa68d9991815..2a74202cbfb698960d3ecd6c0cab7fcf4fdc59e6 100644 --- a/ee/lib/ee/gitlab/git_access_project.rb +++ b/ee/lib/ee/gitlab/git_access_project.rb @@ -31,6 +31,10 @@ def check_namespace! def allowed_access_namespace? # Verify namespace access only on initial call from Gitlab Shell and Workhorse return true unless changes == ::Gitlab::GitAccess::ANY + + # Check if SSH keys are disabled for enterprise users + return false if ssh_keys_disabled_for_user? + # Return early if ssh certificate feature is not enabled for namespace # If allowed_namespace_path is passed anyway, we return false # It may happen, when a user authenticates via SSH certificate and tries accessing to personal namespace @@ -43,6 +47,17 @@ def allowed_access_namespace? allowed_namespace.present? && namespace.root_ancestor.id == allowed_namespace.id end + # Verify that SSH keys are disabled for enterprise users and the + # actor is a Key + # Deploy keys are allowed anyway + def ssh_keys_disabled_for_user? + return false unless actor.instance_of?(::Key) + return false unless user&.enterprise_user? + return false unless user.enterprise_group&.disable_ssh_keys? + + user.human? + end + # Verify that enabled_git_access_protocol is ssh_certificates and the # actor is either User or Key # Deploy keys are allowed anyway diff --git a/ee/lib/ee/gitlab/saas.rb b/ee/lib/ee/gitlab/saas.rb index 3a8ae0127e77ceedea0178305f17e7714333c4af..c8db0eb5a49cb356f8abc9356ab98d5fedf6f81e 100644 --- a/ee/lib/ee/gitlab/saas.rb +++ b/ee/lib/ee/gitlab/saas.rb @@ -32,6 +32,7 @@ module Saas gitlab_duo_saas_only pipl_compliance disable_personal_access_tokens + disable_ssh_keys ci_runners_allowed_plans secret_detection_service ci_component_usages_in_projects diff --git a/ee/lib/ee/sidebars/user_settings/menus/ssh_keys_menu.rb b/ee/lib/ee/sidebars/user_settings/menus/ssh_keys_menu.rb new file mode 100644 index 0000000000000000000000000000000000000000..03ab35e9c210993c78ca7c5384614e78cb336b30 --- /dev/null +++ b/ee/lib/ee/sidebars/user_settings/menus/ssh_keys_menu.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module EE + module Sidebars # rubocop:disable Gitlab/BoundedContexts -- prepended class is not inside a bounded context namespace + module UserSettings + module Menus + module SshKeysMenu + extend ::Gitlab::Utils::Override + + override :render? + def render? + return false if context.current_user&.enterprise_group&.disable_ssh_keys? + + super + end + end + end + end + end +end diff --git a/ee/lib/namespaces/namespace_setting_changes_auditor.rb b/ee/lib/namespaces/namespace_setting_changes_auditor.rb index a9f0a663fe0366ae91320949f9327e82d7f7d56e..65014cfec5bf8f48042428fe183123a8067168d7 100644 --- a/ee/lib/namespaces/namespace_setting_changes_auditor.rb +++ b/ee/lib/namespaces/namespace_setting_changes_auditor.rb @@ -18,6 +18,7 @@ class NamespaceSettingChangesAuditor < ::AuditEvents::BaseChangesAuditor service_access_tokens_expiration_enforced: 'service_access_tokens_expiration_enforced_updated', enforce_ssh_certificates: 'enforce_ssh_certificates_updated', disable_personal_access_tokens: 'disable_personal_access_tokens_updated', + disable_ssh_keys: 'disable_ssh_keys_updated', remove_dormant_members: 'remove_dormant_members_updated', remove_dormant_members_period: 'remove_dormant_members_period_updated', prevent_sharing_groups_outside_hierarchy: 'prevent_sharing_groups_outside_hierarchy_updated', diff --git a/ee/spec/lib/ee/gitlab/git_access_project_spec.rb b/ee/spec/lib/ee/gitlab/git_access_project_spec.rb index 16d5e013575df19dedc6bc1cb46b3a29312987b6..158282e2278d9c3499b3e6dda2bb8f83eebc7896 100644 --- a/ee/spec/lib/ee/gitlab/git_access_project_spec.rb +++ b/ee/spec/lib/ee/gitlab/git_access_project_spec.rb @@ -387,4 +387,62 @@ def simulate_quarantine_size(repository, size) end end end + + describe 'SSH key access control', feature_category: :system_access do + let_it_be_with_reload(:enterprise_group) { create(:group) } + let_it_be(:project) { create(:project, :repository, namespace: enterprise_group) } + let_it_be_with_reload(:enterprise_user) { create(:user, enterprise_group_id: enterprise_group.id) } + let_it_be(:key) { create(:key, user: enterprise_user) } + + before do + stub_licensed_features(disable_ssh_keys: true) + stub_saas_features(disable_ssh_keys: true) + stub_feature_flags(enterprise_disable_ssh_keys: true) + project.add_developer(enterprise_user) + enterprise_group.namespace_settings.update!(disable_ssh_keys: true) + end + + def git_access_with_key(user_key) + described_class.new( + user_key, + project, + 'ssh', + authentication_abilities: %i[read_project download_code push_code], + repository_path: "#{project.full_path}.git" + ) + end + + it 'blocks SSH access for enterprise users' do + access = git_access_with_key(key) + + expect { access.check('git-upload-pack', '_any') }.to raise_error( + Gitlab::GitAccess::ForbiddenError, + 'You are not allowed to access projects in this namespace.' + ) + end + + it 'allows deploy key access' do + deploy_key = create(:deploy_key) + project.deploy_keys << deploy_key + access = git_access_with_key(deploy_key) + + expect { access.check('git-upload-pack', '_any') }.not_to raise_error + end + + it 'allows non-enterprise users' do + non_enterprise_user = create(:user) + non_enterprise_key = create(:key, user: non_enterprise_user) + project.add_developer(non_enterprise_user) + access = git_access_with_key(non_enterprise_key) + + expect { access.check('git-upload-pack', '_any') }.not_to raise_error + end + + it 'allows access when group setting is disabled' do + enterprise_group.namespace_settings.update!(disable_ssh_keys: false) + access = git_access_with_key(key) + + expect { access.check('git-upload-pack', '_any') }.not_to raise_error + end + end end diff --git a/ee/spec/models/ee/group_spec.rb b/ee/spec/models/ee/group_spec.rb index d2ec78a0a4fc064724de6b4847de162be65ac28b..28cf8890bdb377116b3da6271ccba768c31cea63 100644 --- a/ee/spec/models/ee/group_spec.rb +++ b/ee/spec/models/ee/group_spec.rb @@ -3113,6 +3113,87 @@ def webhook_headers end end + describe '#disable_ssh_keys?' do + before do + group.update!(disable_ssh_keys: true) + end + + context 'when not licensed' do + it 'returns false even if the database value is true' do + expect(group.disable_ssh_keys?).to be_falsey + end + end + + context 'when licensed' do + before do + stub_licensed_features(disable_ssh_keys: true) + end + + it 'returns false even if the database value is true' do + expect(group.disable_ssh_keys?).to be_falsey + end + + context 'on SaaS', :saas do + before do + stub_saas_features(disable_ssh_keys: true) + stub_feature_flags(enterprise_disable_ssh_keys: true) + end + + it 'returns true' do + expect(group.disable_ssh_keys?).to be_truthy + end + + context 'for a subgroup' do + let(:subgroup) { create(:group, parent: group) } + + it 'returns false even if the database value is true' do + subgroup.update!(disable_ssh_keys: true) + + expect(subgroup.disable_ssh_keys?).to be_falsey + end + end + end + end + end + + describe '#disable_ssh_keys_available?' do + context 'when all requirements met (licensed, SaaS, root group)', :saas do + before do + stub_licensed_features(disable_ssh_keys: true) + stub_saas_features(disable_ssh_keys: true) + stub_feature_flags(enterprise_disable_ssh_keys: true) + end + + it 'returns true for root group' do + expect(group.disable_ssh_keys_available?).to be_truthy + end + + it 'returns false for subgroup' do + subgroup = create(:group, parent: group) + expect(subgroup.disable_ssh_keys_available?).to be_falsey + end + end + + context 'when requirements not met' do + it 'returns false when not licensed' do + expect(group.disable_ssh_keys_available?).to be_falsey + end + + it 'returns false when licensed but not SaaS' do + stub_licensed_features(disable_ssh_keys: true) + expect(group.disable_ssh_keys_available?).to be_falsey + end + + it 'returns false when feature flag is disabled', :saas do + stub_licensed_features(disable_ssh_keys: true) + stub_saas_features(disable_ssh_keys: true) + stub_feature_flags(enterprise_disable_ssh_keys: false) + + expect(group.disable_ssh_keys_available?).to be_falsey + end + end + end + describe '#hide_email_on_profile?' do it 'returns false by default' do expect(group.hide_email_on_profile?).to be_falsey diff --git a/ee/spec/requests/ee/user_settings/ssh_keys_controller_spec.rb b/ee/spec/requests/ee/user_settings/ssh_keys_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d8958703e750593143b82eed0dc139f0e659c1ec --- /dev/null +++ b/ee/spec/requests/ee/user_settings/ssh_keys_controller_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe UserSettings::SshKeysController, feature_category: :source_code_management do + let_it_be(:group) { create(:group) } + let_it_be(:enterprise_user) { create(:user, enterprise_group_id: group.id) } + let_it_be(:regular_user) { create(:user, :with_namespace) } + + before do + stub_licensed_features(disable_ssh_keys: true) + stub_saas_features(disable_ssh_keys: true) + stub_feature_flags(enterprise_disable_ssh_keys: true) + end + + describe 'SSH key disabling for enterprise users' do + context 'when group has SSH keys disabled' do + before do + group.namespace_settings.update!(disable_ssh_keys: true) + end + + it 'blocks access to SSH key management for enterprise users' do + login_as(enterprise_user) + + get user_settings_ssh_keys_path + expect(response).to have_gitlab_http_status(:not_found) + + post user_settings_ssh_keys_path, params: { key: build(:key).attributes } + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'does not block non-enterprise users' do + login_as(regular_user) + + component_double = instance_double(Namespaces::Storage::NamespaceLimit::PreEnforcementAlertComponent, + render?: false, + render_in: nil) + allow(Namespaces::Storage::NamespaceLimit::PreEnforcementAlertComponent) + .to receive(:new).and_return(component_double) + + get user_settings_ssh_keys_path + expect(response).to have_gitlab_http_status(:ok) + + post user_settings_ssh_keys_path, params: { key: build(:key).attributes } + expect(response).to have_gitlab_http_status(:found) + end + end + end +end diff --git a/ee/spec/requests/groups_controller_spec.rb b/ee/spec/requests/groups_controller_spec.rb index 8e6410432b8ed0aa4b3f766fba3b43e138f24ffc..86aa9bd37a352236be7eefa95b7998c66ff64578 100644 --- a/ee/spec/requests/groups_controller_spec.rb +++ b/ee/spec/requests/groups_controller_spec.rb @@ -618,6 +618,44 @@ end end + context 'setting disable_ssh_keys', :saas do + let(:params) { { group: { disable_ssh_keys: true } } } + + before do + stub_licensed_features(disable_ssh_keys: true) + stub_saas_features(disable_ssh_keys: true) + stub_feature_flags(enterprise_disable_ssh_keys: true) + end + + context 'when user is a group owner' do + before do + group.add_owner(user) + end + + it 'successfully updates the setting' do + expect { request }.to change { + group.reload.namespace_settings.disable_ssh_keys? + }.from(false).to(true) + + expect(response).to have_gitlab_http_status(:found) + end + end + + context 'when user is not a group owner' do + before do + group.add_maintainer(user) + end + + it 'does not change the setting and returns not found' do + expect { request }.not_to change { + group.reload.namespace_settings.disable_ssh_keys? + }.from(false) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + context 'when setting hide_email_on_profile' do let(:params) { { group: { hide_email_on_profile: true } } } diff --git a/lib/sidebars/user_settings/menus/ssh_keys_menu.rb b/lib/sidebars/user_settings/menus/ssh_keys_menu.rb index 011de7641c72c0546600bd247e98a3a79925e4d3..859ec2187e3c3af33b59c633cc9a73ffb9a18ece 100644 --- a/lib/sidebars/user_settings/menus/ssh_keys_menu.rb +++ b/lib/sidebars/user_settings/menus/ssh_keys_menu.rb @@ -34,3 +34,5 @@ def extra_container_html_options end end end + +Sidebars::UserSettings::Menus::SshKeysMenu.prepend_mod diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1012e136a2030d043fb5572c94463712224402b3..9bbe2d5fa6220245e797a81315d62648a87400b5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -32475,6 +32475,9 @@ msgstr "" msgid "GroupSettings|Delete immediately" msgstr "" +msgid "GroupSettings|Disable SSH Keys" +msgstr "" + msgid "GroupSettings|Disable personal access tokens" msgstr "" @@ -32547,6 +32550,9 @@ msgstr "" msgid "GroupSettings|How do I manage group SSH certificates?" msgstr "" +msgid "GroupSettings|If enabled, enterprise users cannot use SSH Keys. %{learn_more_link}." +msgstr "" + msgid "GroupSettings|If enabled, enterprise users cannot use personal access tokens. %{learn_more_link}." msgstr ""